feat: use LLM to autonomously parse text itineraries into scheduled reminders
This commit is contained in:
parent
aab9e60100
commit
294f10698e
|
|
@ -109,6 +109,93 @@ async function getAiInsight(activityDetails, location, modelName = 'qwen2.5:0.5b
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateAiSchedule(activities, meals, date, trip) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const prompt = `You are a strict JSON API scheduling agent. Extract a daily schedule from these plans:
|
||||||
|
Activities: ${activities}
|
||||||
|
Meals: ${meals}
|
||||||
|
Date: ${date}
|
||||||
|
|
||||||
|
Return ONLY a JSON array of objects. Each object must have "time" (in 24-hour HH:mm format) and "message" (a short, fun 1-sentence reminder). Estimate reasonable times if none are given (e.g. breakfast 8:00, lunch 12:30, dinner 18:30). DO NOT output any markdown or conversational text.`;
|
||||||
|
|
||||||
|
const modelName = trip.llmModel || 'qwen2.5:0.5b';
|
||||||
|
// Support future providers via trip.llmProvider
|
||||||
|
// Defaulting to Ollama for now
|
||||||
|
const body = JSON.stringify({ model: modelName, prompt, stream: false });
|
||||||
|
const url = new URL(OLLAMA_URL);
|
||||||
|
|
||||||
|
const req = http.request({
|
||||||
|
hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
||||||
|
}, (res) => {
|
||||||
|
let d = '';
|
||||||
|
res.on('data', c => d += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const raw = JSON.parse(d).response;
|
||||||
|
// Extract JSON array using regex in case of conversational wrapper
|
||||||
|
const match = raw.match(/\[[\s\S]*\]/);
|
||||||
|
if (match) {
|
||||||
|
resolve(JSON.parse(match[0]));
|
||||||
|
} else {
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[TripService] AI Schedule parsing failed:', e.message);
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', () => resolve([]));
|
||||||
|
req.write(body);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleRemindersBatch(events, trip, dateStr) {
|
||||||
|
if (!events || events.length === 0) return;
|
||||||
|
|
||||||
|
// Base date object for today in Eastern Time (approximated locally)
|
||||||
|
const baseDate = new Date(dateStr + "T00:00:00-04:00");
|
||||||
|
|
||||||
|
const items = events.map(ev => {
|
||||||
|
const [hh, mm] = ev.time.split(':');
|
||||||
|
const eventTime = new Date(baseDate);
|
||||||
|
eventTime.setHours(parseInt(hh, 10));
|
||||||
|
eventTime.setMinutes(parseInt(mm, 10));
|
||||||
|
|
||||||
|
// Send reminder 15 minutes before the event time
|
||||||
|
const sendAt = new Date(eventTime.getTime() - 15 * 60000);
|
||||||
|
|
||||||
|
// Don't schedule in the past
|
||||||
|
if (sendAt <= new Date()) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: `⏰ *${trip.name} Schedule Alert*\n\n${ev.message}\n\n_Starts at ${ev.time}_`,
|
||||||
|
sendAt: sendAt.toISOString(),
|
||||||
|
label: `${trip.name} - ${ev.message.slice(0, 30)}...`,
|
||||||
|
to: trip.sendToGroup && trip.groupUrls && trip.groupUrls.length > 0 ? trip.groupUrls[0] : (GROUP_URL || PERSONAL_NUM)
|
||||||
|
};
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
if (items.length === 0) return;
|
||||||
|
|
||||||
|
const body = JSON.stringify({ items });
|
||||||
|
const req = http.request({
|
||||||
|
hostname: 'reminder-service.ai-agents.svc.cluster.local',
|
||||||
|
port: 6001,
|
||||||
|
path: '/reminders/batch',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
||||||
|
}, (res) => {
|
||||||
|
res.on('data', () => {});
|
||||||
|
res.on('end', () => console.log(`[TripService] Batch queued ${items.length} reminders via reminder-service`));
|
||||||
|
});
|
||||||
|
req.on('error', (e) => console.error('[TripService] Failed to queue reminders:', e.message));
|
||||||
|
req.write(body);
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
function sendWhatsApp(to, message) {
|
function sendWhatsApp(to, message) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const body = JSON.stringify({ to, message });
|
const body = JSON.stringify({ to, message });
|
||||||
|
|
@ -155,6 +242,15 @@ async function generateAndSendBriefing(trip, dayPlan) {
|
||||||
for (const group of groupsToNotify) {
|
for (const group of groupsToNotify) {
|
||||||
await sendWhatsApp(group, message);
|
await sendWhatsApp(group, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate and schedule detailed AI itinerary
|
||||||
|
console.log(`[TripService] Generating detailed AI schedule for ${trip.name} - ${dayPlan.date}`);
|
||||||
|
const scheduleEvents = await generateAiSchedule(dayPlan.activities, dayPlan.meals, dayPlan.date, trip);
|
||||||
|
if (scheduleEvents.length > 0) {
|
||||||
|
scheduleRemindersBatch(scheduleEvents, trip, dayPlan.date);
|
||||||
|
} else {
|
||||||
|
console.log(`[TripService] AI yielded no parseable schedule events.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Routes ───────────────────────────────────────────────────────────────────
|
// ── Routes ───────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue