diff --git a/js/app.js b/js/app.js index 9bfd993..2ad8c28 100644 --- a/js/app.js +++ b/js/app.js @@ -431,11 +431,30 @@ function renderTasks() { completed.push(t); return; } + pending.push(t); + + if (!t.due_at) { + thisWeek.push(t); + return; + } + const d = new Date(t.due_at); + + // Ignore invalid dates + if (isNaN(d.getTime())) { + thisWeek.push(t); + return; + } + const diffDays = (d - now) / (1000 * 60 * 60 * 24); - if (diffDays <= 3) dueSoon.push(t); - else thisWeek.push(t); + + // Due soon = upcoming within 3 days only + if (diffDays >= 0 && diffDays <= 3) { + dueSoon.push(t); + } else { + thisWeek.push(t); + } }); } @@ -751,6 +770,34 @@ function renderCalendar() { }); } +function findSubjectForExtractedItem(item) { + const requested = String(item.subject_name || '').trim().toLowerCase(); + const defaultSubject = store.subjects[0] || null; + if (!requested || requested === 'general') return defaultSubject; + + return store.subjects.find(s => s.name.toLowerCase() === requested) + || store.subjects.find(s => s.name.toLowerCase().includes(requested) || requested.includes(s.name.toLowerCase())) + || defaultSubject; +} + +function prepareExtractedTasksForSave(items) { + return (items || []).map(item => { + const sub = store.subjects.find(s => String(s.id) === String(item.subject_id)) + || findSubjectForExtractedItem(item); + + return { + title: String(item.title || '').trim(), + subject_id: sub?.id, + due_at: item.due_at, + notes: item.notes || '', + priority: item.priority || 'medium', + confidence_score: item.confidence_score || 60, + status: item.status || 'Not Started', + archived: 0 + }; + }).filter(item => item.title && item.subject_id && item.due_at); +} + function renderExtraction() { const pasteItems = store.currentPaste; if (!pasteItems || pasteItems.length === 0) { @@ -766,7 +813,8 @@ function renderExtraction() { let html = `
Extracted — ${pasteItems.length} items
`; pasteItems.forEach((item, index) => { // try to match subject name - const sub = store.subjects.find(s => s.name.toLowerCase().includes((item.subject_name || '').toLowerCase())) || store.subjects[3]; + const sub = findSubjectForExtractedItem(item); + if (!sub) return; // Attach subject id to item so Add will work item.subject_id = sub.id; @@ -1036,13 +1084,6 @@ newTaskSave.addEventListener('click', async () => { newTaskModal.style.display = 'none'; }); -addItemsBtn.addEventListener('click', () => { - if (store.currentPaste) { - store.addTasks(store.currentPaste); - store.clearExtracted(); - pasteInput.value = ''; - } -}); }); extractBtn.addEventListener('click', async () => { @@ -1065,11 +1106,13 @@ clearBtn.addEventListener('click', () => { store.clearExtracted(); }); -addItemsBtn.addEventListener('click', () => { +addItemsBtn.addEventListener('click', async () => { if (store.currentPaste) { - store.addTasks(store.currentPaste); - store.clearExtracted(); - pasteInput.value = ''; + const added = await store.addTasks(prepareExtractedTasksForSave(store.currentPaste)); + if (added) { + store.clearExtracted(); + pasteInput.value = ''; + } } }); diff --git a/js/store.js b/js/store.js index 5d83807..5c0d83c 100644 --- a/js/store.js +++ b/js/store.js @@ -65,10 +65,19 @@ export const store = { // ================= UPDATED FUNCTION ================= async addTasks(newTasks) { try { + const tasksToAdd = (Array.isArray(newTasks) ? newTasks : [newTasks]) + .filter(Boolean) + .map(({ _isEditing, icon, subject_name, ...task }) => task); + + if (tasksToAdd.length === 0) { + alert("No valid tasks to add"); + return false; + } + const res = await fetch('/api/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newTasks) + body: JSON.stringify(tasksToAdd) }); const data = await res.json(); // always parse response @@ -77,7 +86,7 @@ export const store = { // Backend error alert(`❌ ${data.message || "Failed to add tasks"}`); console.error('Add task error:', data); - return; + return false; } // ================= USER MESSAGES ================= @@ -102,6 +111,7 @@ export const store = { const tasksRes = await fetch('/api/tasks'); this.tasks = await tasksRes.json(); this.notify(); + return data.inserted > 0; } catch (e) { console.error('Failed to add tasks', e); @@ -294,7 +304,9 @@ export const store = { }, setExtracted(items) { - this.currentPaste = items.map(item => ({ ...item, _isEditing: false })); + this.currentPaste = Array.isArray(items) + ? items.map(item => ({ ...item, _isEditing: false })) + : []; this.notify(); }, diff --git a/js/utils/api.js b/js/utils/api.js index 063ebaa..a2a903d 100644 --- a/js/utils/api.js +++ b/js/utils/api.js @@ -11,9 +11,33 @@ export async function extractTasksFromText(text) { return []; } - return await res.json(); + return normalizeExtractedTasks(await res.json()); } catch (e) { console.error('Error hitting extract endpoint', e); return []; } } + +function normalizeExtractedTasks(data) { + const items = Array.isArray(data) + ? data + : Array.isArray(data?.tasks) + ? data.tasks + : Array.isArray(data?.items) + ? data.items + : data && typeof data === 'object' + ? [data] + : []; + + return items + .filter(item => item && typeof item === 'object') + .map(item => ({ + ...item, + title: String(item.title || '').trim(), + subject_name: String(item.subject_name || item.subject || 'General').trim(), + notes: String(item.notes || '').trim(), + confidence_score: Number(item.confidence_score ?? item.confidence ?? 60), + priority: item.priority || 'medium' + })) + .filter(item => item.title && item.due_at); +} diff --git a/js/utils/nlpDateExtractor.js b/js/utils/nlpDateExtractor.js index e6d23c9..b607696 100644 --- a/js/utils/nlpDateExtractor.js +++ b/js/utils/nlpDateExtractor.js @@ -1,20 +1,21 @@ export function extractDate(text, now = new Date()) { - const lower = text.toLowerCase(); + const lower = String(text || '').toLowerCase(); + const time = extractTime(lower); const relativeDay = matchRelativeDay(lower, now); - if (relativeDay) return withTime(relativeDay, extractTime(lower)); + if (relativeDay) return withTime(relativeDay, time); const inN = matchInNDaysWeeks(lower, now); - if (inN) return withTime(inN, extractTime(lower)); + if (inN) return withTime(inN, time); const weekday = matchNamedWeekday(lower, now); - if (weekday) return withTime(weekday, extractTime(lower)); + if (weekday) return withTime(weekday, time); const absolute = matchAbsoluteDate(lower, now); - if (absolute) return withTime(absolute, extractTime(lower)); + if (absolute) return withTime(absolute, time); const eop = matchEndOfPeriod(lower, now); - if (eop) return withTime(eop, extractTime(lower)); + if (eop) return withTime(eop, time); return null; } @@ -25,35 +26,30 @@ function addDays(date, n) { return d; } -function startOf(date) { +function endOfDay(date) { const d = new Date(date); - d.setHours(23, 59, 0, 0); + d.setHours(23, 59, 0, 0); return d; } -function toISO(date) { - return date.toISOString(); -} - -function withTime(dateStr, timeParts) { - const d = new Date(dateStr); +function withTime(date, timeParts) { + const d = new Date(date); if (timeParts) { d.setHours(timeParts.hours, timeParts.minutes, 0, 0); } - return toISO(d); + return d.toISOString(); } export function extractTime(text) { - const lower = text.toLowerCase(); + const lower = String(text || '').toLowerCase(); if (/\bmidnight\b/.test(lower)) return { hours: 23, minutes: 59 }; if (/\bnoon\b/.test(lower)) return { hours: 12, minutes: 0 }; - // HH:MM am/pm let m = lower.match(/\b(\d{1,2}):(\d{2})\s*(am|pm)?\b/); if (m) { - let h = parseInt(m[1]); - const min = parseInt(m[2]); + let h = parseInt(m[1], 10); + const min = parseInt(m[2], 10); const meridiem = m[3]; if (meridiem === 'pm' && h < 12) h += 12; if (meridiem === 'am' && h === 12) h = 0; @@ -62,7 +58,7 @@ export function extractTime(text) { m = lower.match(/\b(\d{1,2})\s*(am|pm)\b/); if (m) { - let h = parseInt(m[1]); + let h = parseInt(m[1], 10); const meridiem = m[2]; if (meridiem === 'pm' && h < 12) h += 12; if (meridiem === 'am' && h === 12) h = 0; @@ -73,58 +69,48 @@ export function extractTime(text) { } function matchRelativeDay(lower, now) { - if (/\btoday\b/.test(lower)) return toISO(startOf(now)); - if (/\btomorrow\b/.test(lower)) return toISO(startOf(addDays(now, 1))); - if (/\bday after tomorrow\b/.test(lower)) return toISO(startOf(addDays(now, 2))); - if (/\byesterday\b/.test(lower)) return toISO(startOf(addDays(now, -1))); // edge case + if (/\btoday\b/.test(lower) || /\btonight\b/.test(lower)) return endOfDay(now); + if (/\bday after tomorrow\b/.test(lower)) return endOfDay(addDays(now, 2)); + if (/\btomorrow\b/.test(lower)) return endOfDay(addDays(now, 1)); + if (/\byesterday\b/.test(lower)) return endOfDay(addDays(now, -1)); return null; } function matchInNDaysWeeks(lower, now) { - let m = lower.match(/\bin\s+(\d+|a|an|one|two|three|four|five|six|seven)\s+(day|days|week|weeks|month|months)\b/); + const m = lower.match(/\bin\s+(\d+|a|an|one|two|three|four|five|six|seven)\s+(day|days|week|weeks|month|months)\b/); if (!m) return null; - const rawN = m[1]; - const unit = m[2]; const wordMap = { a: 1, an: 1, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7 }; - const n = isNaN(rawN) ? (wordMap[rawN] || 1) : parseInt(rawN); + const n = Number.isNaN(Number(m[1])) ? (wordMap[m[1]] || 1) : parseInt(m[1], 10); + const unit = m[2]; let d = new Date(now); if (unit.startsWith('day')) d = addDays(d, n); else if (unit.startsWith('week')) d = addDays(d, n * 7); - else if (unit.startsWith('month')) d.setMonth(d.getMonth() + n); + else d.setMonth(d.getMonth() + n); - return toISO(startOf(d)); + return endOfDay(d); } -const WEEKDAYS = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']; + +const WEEKDAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; function matchNamedWeekday(lower, now) { - const pattern = new RegExp( - `\\b(next|this|on|coming)?\\s*(${WEEKDAYS.join('|')})\\b` - ); + const pattern = new RegExp(`\\b(next|this|on|coming)?\\s*(${WEEKDAYS.join('|')})\\b`); const m = lower.match(pattern); if (!m) return null; const qualifier = m[1] || ''; const targetDay = WEEKDAYS.indexOf(m[2]); const currentDay = now.getDay(); - let diff = targetDay - currentDay; - if (qualifier === 'next') { - // Always go to NEXT week's instance - if (diff <= 0) diff += 7; - diff += (diff === 0 ? 7 : 0); // if same day, also push a week - } else if (qualifier === 'this') { - // This week — if already passed, stay same - if (diff < 0) diff += 7; - } else { - // No qualifier: if day already passed today, go to next week - if (diff <= 0) diff += 7; - } + if (qualifier === 'next') diff = diff <= 0 ? diff + 7 : diff + 7; + else if (diff <= 0 && qualifier !== 'this') diff += 7; + else if (diff < 0) diff += 7; - return toISO(startOf(addDays(now, diff))); + return endOfDay(addDays(now, diff)); } + const MONTHS = { jan: 0, january: 0, feb: 1, february: 1, @@ -141,35 +127,26 @@ const MONTHS = { }; function matchAbsoluteDate(lower, now) { - // "april 25", "25 april", "25th april", "april 25th" const monthNames = Object.keys(MONTHS).join('|'); const ordinal = `(\\d{1,2})(?:st|nd|rd|th)?`; - - let m; - - m = lower.match(new RegExp(`\\b(${monthNames})\\s+${ordinal}\\b`)); - if (m) { - const month = MONTHS[m[1]]; - const day = parseInt(m[2]); - return toISO(startOf(resolveYear(now, month, day))); - } + let m = lower.match(new RegExp(`\\b(${monthNames})\\s+${ordinal}\\b`)); + if (m) return endOfDay(resolveYear(now, MONTHS[m[1]], parseInt(m[2], 10))); m = lower.match(new RegExp(`\\b${ordinal}\\s+(${monthNames})\\b`)); - if (m) { - const day = parseInt(m[1]); - const month = MONTHS[m[2]]; - return toISO(startOf(resolveYear(now, month, day))); - } + if (m) return endOfDay(resolveYear(now, MONTHS[m[2]], parseInt(m[1], 10))); - m = lower.match(/\b(\d{1,2})[\/\-\.](\d{1,2})(?:[\/\-\.](\d{2,4}))?\b/); - if (m) { - const day = parseInt(m[1]); - const month = parseInt(m[2]) - 1; - const year = m[3] ? (m[3].length === 2 ? 2000 + parseInt(m[3]) : parseInt(m[3])) : null; - return toISO(startOf(resolveYear(now, month, day, year))); - } + m = lower.match(/\b(\d{1,2})[\/\-.](\d{1,2})(?:[\/\-.](\d{2,4}))?\b/); + if (!m) return null; - return null; + const first = parseInt(m[1], 10); + const second = parseInt(m[2], 10); + const day = first > 12 ? first : second > 12 ? second : first; + const month = first > 12 ? second - 1 : second > 12 ? first - 1 : second - 1; + const year = m[3] + ? (m[3].length === 2 ? 2000 + parseInt(m[3], 10) : parseInt(m[3], 10)) + : null; + + return endOfDay(resolveYear(now, month, day, year)); } function resolveYear(now, month, day, explicitYear = null) { @@ -181,23 +158,18 @@ function resolveYear(now, month, day, explicitYear = null) { function matchEndOfPeriod(lower, now) { if (/\bend of (the\s+)?week\b/.test(lower) || /\bby (the\s+)?weekend\b/.test(lower)) { - // Next Sunday - const d = new Date(now); - d.setDate(d.getDate() + (7 - d.getDay())); - return toISO(startOf(d)); + return endOfDay(addDays(now, 7 - now.getDay())); } if (/\bend of (the\s+)?month\b/.test(lower)) { - const d = new Date(now.getFullYear(), now.getMonth() + 1, 0); // last day of month - return toISO(startOf(d)); + return endOfDay(new Date(now.getFullYear(), now.getMonth() + 1, 0)); } if (/\bend of (the\s+)?year\b/.test(lower)) { - const d = new Date(now.getFullYear(), 11, 31); - return toISO(startOf(d)); + return endOfDay(new Date(now.getFullYear(), 11, 31)); } if (/\bnext month\b/.test(lower)) { const d = new Date(now); d.setMonth(d.getMonth() + 1); - return toISO(startOf(d)); + return endOfDay(d); } return null; -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index d4124c7..5ec4691 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,9 +67,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { @@ -79,13 +79,12 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { @@ -95,9 +94,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { @@ -113,9 +112,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, "node_modules/@types/node": { @@ -1363,24 +1362,24 @@ } }, "node_modules/protobufjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", - "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.0.tgz", + "integrity": "sha512-LtESOsMPTZgyYtwxhvdgdjGL0HmXEaRA/hVD6sol4zA60hVXXXP/SGmxnqDbgGE8gy7pYex7cym+5vYPcmaXBQ==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", + "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", + "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -1936,9 +1935,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/server.js b/server.js index 877f766..84bfe40 100644 --- a/server.js +++ b/server.js @@ -79,6 +79,7 @@ function nlpExtractDate(text, now = new Date()) { const time = nlpExtractTime(lower); if (/\btoday\b/.test(lower)) return nlpWithTime(nlpStartOf(now).toISOString(), time); + if (/\btonight\b/.test(lower)) return nlpWithTime(nlpStartOf(now).toISOString(), time); if (/\bday after tomorrow\b/.test(lower)) return nlpWithTime(nlpStartOf(nlpAddDays(now, 2)).toISOString(), time); if (/\btomorrow\b/.test(lower)) return nlpWithTime(nlpStartOf(nlpAddDays(now, 1)).toISOString(), time); @@ -133,7 +134,7 @@ function nlpExtractDate(text, now = new Date()) { } const NLP_SUBJECT_KEYWORDS = { - 'Computer Science': ['cs','computer science','programming','code','coding','algorithm','data structure','software','python','java','javascript','html','database','sql','network','operating system','os','web','scheduling','lab report','assignment'], + 'Computer Science': ['cs','computer science','programming','code','coding','algorithm','data structure','software','python','java','javascript','html','database','sql','network','operating system','os','web','scheduling','lab report'], 'Mathematics': ['maths','math','mathematics','calculus','algebra','statistics','probability','theorem','equation','integral','derivative','matrix','vector','problem set','pset','worksheet','integration'], 'English Lit': ['english','literature','essay','novel','poem','poetry','shakespeare','writing','prose','narrative','analysis','literary','book report','reading','thesis','draft','revision'], 'Physics': ['physics','mechanics','thermodynamics','optics','velocity','acceleration','force','energy','momentum','lab','experiment','wave','circuit','resistance','voltage'], @@ -144,12 +145,18 @@ function nlpDetectSubject(text) { const scores = {}; for (const [sub, kws] of Object.entries(NLP_SUBJECT_KEYWORDS)) { scores[sub] = 0; + const subjectWords = sub.toLowerCase().split(/\s+/).filter(Boolean); + if (lower.includes(sub.toLowerCase())) scores[sub] += 12; + if (subjectWords.length > 1 && subjectWords.every(word => lower.includes(word))) scores[sub] += 8; for (const kw of kws) { const re = new RegExp(`\\b${kw.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')}\\b`, 'gi'); const hits = lower.match(re); if (hits) scores[sub] += hits.length * (kw.length > 5 ? 2 : 1); } } + if (/\benglish\s+lit\b|\blit\s+assignment\b/.test(lower)) scores['English Lit'] += 10; + if (/\bmaths?\b|\bmathematics\b/.test(lower)) scores['Mathematics'] += 10; + if (/\bphysics\b/.test(lower)) scores['Physics'] += 10; const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0]; return best && best[1] > 0 ? best[0] : null; } @@ -165,7 +172,7 @@ function nlpTaskScore(seg) { const lower = seg.toLowerCase(); let s = 0; for (const v of NLP_TASK_VERBS) if (lower.includes(v)) { s += 30; break; } - const sigs = ['due','deadline','by','before','submit','tomorrow','next','today','week','month', + const sigs = ['due','deadline','by','before','submit','tomorrow','tonight','next','today','week','month', 'monday','tuesday','wednesday','thursday','friday','saturday','sunday', /\d+\/\d+/, /\d{1,2}(st|nd|rd|th)/]; for (const sig of sigs) if (sig instanceof RegExp ? sig.test(lower) : lower.includes(sig)) { s += 25; break; } @@ -508,4 +515,4 @@ app.use((err, req, res, next) => { const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log('Server running on port ' + PORT); -}); \ No newline at end of file +});