From 23b382c3986f945c0c184e0fc08479dd78b85097 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Fri, 15 Aug 2025 22:13:14 -0400 Subject: [PATCH] bulk ai event creation --- src/app/api/ai-event/route.ts | 22 +++++++++------- src/app/page.tsx | 47 +++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/app/api/ai-event/route.ts b/src/app/api/ai-event/route.ts index b91d99c..5ddd70d 100644 --- a/src/app/api/ai-event/route.ts +++ b/src/app/api/ai-event/route.ts @@ -4,9 +4,10 @@ export async function POST(request: Request) { const { prompt } = await request.json(); const systemPrompt = ` -You are an assistant that converts natural language event descriptions into JSON objects -matching this TypeScript type EXACTLY: +You are an assistant that converts natural language requests into an ARRAY of JSON calendar events. +TypeScript interface: { + id?: string, title: string, description?: string, location?: string, @@ -14,11 +15,15 @@ matching this TypeScript type EXACTLY: start: string, // ISO datetime like 2024-06-14T13:00:00Z end?: string, allDay?: boolean -} -Today is ${new Date().toISOString().split("T")[0]}. -If no time is given, assume allDay event. -If no end time is given and not allDay, make it 1 hour after start. -Output ONLY valid JSON, nothing else. +}[] + +Rules: +- If the user describes multiple events in one prompt, return multiple objects (one per event). +- Always return a valid JSON array of objects, even if there's only one event. +- Today is ${new Date().toLocaleString()}. +- If no time is given, assume allDay event. +- If no end time is given (and event is not allDay), default to 1 hour after start. +Output ONLY valid JSON (no prose). `; const res = await fetch("https://openrouter.ai/api/v1/chat/completions", { @@ -28,7 +33,7 @@ Output ONLY valid JSON, nothing else. "Content-Type": "application/json", }, body: JSON.stringify({ - model: "openai/gpt-3.5-turbo", // Or 'mistral/mistral-tiny' for cheaper + model: "openai/gpt-4.1-nano", messages: [ { role: "system", content: systemPrompt }, { role: "user", content: prompt }, @@ -37,7 +42,6 @@ Output ONLY valid JSON, nothing else. }); const data = await res.json(); - try { const content = data.choices[0].message.content; const parsed = JSON.parse(content); diff --git a/src/app/page.tsx b/src/app/page.tsx index 2741f56..bfd4500 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -139,21 +139,42 @@ export default function HomePage() { body: JSON.stringify({ prompt: aiPrompt }) }) const data = await res.json() - if (data.title) { - setTitle(data.title || '') - setDescription(data.description || '') - setLocation(data.location || '') - setUrl(data.url || '') - setStart(data.start || '') - setEnd(data.end || '') - setAllDay(data.allDay || false) - setEditingId(null) - setDialogOpen(true) + + if (Array.isArray(data) && data.length > 0) { + if (data.length === 1) { + // Prefill dialog directly (same as before) + const ev = data[0] + setTitle(ev.title || '') + setDescription(ev.description || '') + setLocation(ev.location || '') + setUrl(ev.url || '') + setStart(ev.start || '') + setEnd(ev.end || '') + setAllDay(ev.allDay || false) + setEditingId(null) + setDialogOpen(true) + } else { + // Save them all directly to DB + for (const ev of data) { + const newEvent = { + id: nanoid(), + ...ev, + createdAt: new Date().toISOString(), + lastModified: new Date().toISOString(), + } + await addEvent(newEvent) + } + const stored = await getAllEvents() + setEvents(stored) + setSummary(`Added ${data.length} AI-generated events.`) + setSummaryUpdated(new Date().toLocaleString()) + } } else { - alert('AI could not parse event.') + alert('AI did not return event data.') } - } catch { - alert('Error creating event') + } catch (err) { + console.error(err) + alert('Error from AI service.') } finally { setAiLoading(false) }