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 = `
`;
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
+});