bulk ai event creation

This commit is contained in:
2025-08-15 22:13:14 -04:00
parent b4c59bbde0
commit a41d003401
2 changed files with 47 additions and 22 deletions

View File

@@ -4,9 +4,10 @@ export async function POST(request: Request) {
const { prompt } = await request.json(); const { prompt } = await request.json();
const systemPrompt = ` const systemPrompt = `
You are an assistant that converts natural language event descriptions into JSON objects You are an assistant that converts natural language requests into an ARRAY of JSON calendar events.
matching this TypeScript type EXACTLY: TypeScript interface:
{ {
id?: string,
title: string, title: string,
description?: string, description?: string,
location?: string, location?: string,
@@ -14,11 +15,15 @@ matching this TypeScript type EXACTLY:
start: string, // ISO datetime like 2024-06-14T13:00:00Z start: string, // ISO datetime like 2024-06-14T13:00:00Z
end?: string, end?: string,
allDay?: boolean allDay?: boolean
} }[]
Today is ${new Date().toISOString().split("T")[0]}.
If no time is given, assume allDay event. Rules:
If no end time is given and not allDay, make it 1 hour after start. - If the user describes multiple events in one prompt, return multiple objects (one per event).
Output ONLY valid JSON, nothing else. - 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", { 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", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
model: "openai/gpt-3.5-turbo", // Or 'mistral/mistral-tiny' for cheaper model: "openai/gpt-4.1-nano",
messages: [ messages: [
{ role: "system", content: systemPrompt }, { role: "system", content: systemPrompt },
{ role: "user", content: prompt }, { role: "user", content: prompt },
@@ -37,7 +42,6 @@ Output ONLY valid JSON, nothing else.
}); });
const data = await res.json(); const data = await res.json();
try { try {
const content = data.choices[0].message.content; const content = data.choices[0].message.content;
const parsed = JSON.parse(content); const parsed = JSON.parse(content);

View File

@@ -139,21 +139,42 @@ export default function HomePage() {
body: JSON.stringify({ prompt: aiPrompt }) body: JSON.stringify({ prompt: aiPrompt })
}) })
const data = await res.json() const data = await res.json()
if (data.title) {
setTitle(data.title || '') if (Array.isArray(data) && data.length > 0) {
setDescription(data.description || '') if (data.length === 1) {
setLocation(data.location || '') // Prefill dialog directly (same as before)
setUrl(data.url || '') const ev = data[0]
setStart(data.start || '') setTitle(ev.title || '')
setEnd(data.end || '') setDescription(ev.description || '')
setAllDay(data.allDay || false) setLocation(ev.location || '')
setUrl(ev.url || '')
setStart(ev.start || '')
setEnd(ev.end || '')
setAllDay(ev.allDay || false)
setEditingId(null) setEditingId(null)
setDialogOpen(true) setDialogOpen(true)
} else { } else {
alert('AI could not parse event.') // Save them all directly to DB
for (const ev of data) {
const newEvent = {
id: nanoid(),
...ev,
createdAt: new Date().toISOString(),
lastModified: new Date().toISOString(),
} }
} catch { await addEvent(newEvent)
alert('Error creating event') }
const stored = await getAllEvents()
setEvents(stored)
setSummary(`Added ${data.length} AI-generated events.`)
setSummaryUpdated(new Date().toLocaleString())
}
} else {
alert('AI did not return event data.')
}
} catch (err) {
console.error(err)
alert('Error from AI service.')
} finally { } finally {
setAiLoading(false) setAiLoading(false)
} }