From ab96d0b0a0b95472de45848195056e17e2fd02cc Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Fri, 15 Aug 2025 22:46:44 -0400 Subject: [PATCH] add recurrence editor component --- src/app/api/ai-event/route.ts | 24 ++--- src/app/page.tsx | 14 ++- src/components/recurrence-picker.tsx | 129 +++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 src/components/recurrence-picker.tsx diff --git a/src/app/api/ai-event/route.ts b/src/app/api/ai-event/route.ts index 5ddd70d..25ddc16 100644 --- a/src/app/api/ai-event/route.ts +++ b/src/app/api/ai-event/route.ts @@ -4,26 +4,30 @@ export async function POST(request: Request) { const { prompt } = await request.json(); const systemPrompt = ` -You are an assistant that converts natural language requests into an ARRAY of JSON calendar events. -TypeScript interface: +You are an assistant that converts natural language into an ARRAY of calendar events. +TypeScript type: + { id?: string, title: string, description?: string, location?: string, url?: string, - start: string, // ISO datetime like 2024-06-14T13:00:00Z + start: string, // ISO datetime end?: string, - allDay?: boolean + allDay?: boolean, + recurrenceRule?: string // valid iCal RRULE string like FREQ=WEEKLY;BYDAY=MO;INTERVAL=1 }[] 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). + - 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. + - If multiple events are described, return multiple. + - If recurrence is implied (e.g. "every Monday", "daily for 10 days", "monthly on the 15th"), generate a recurrenceRule. + - Output ONLY valid JSON (no prose). `; const res = await fetch("https://openrouter.ai/api/v1/chat/completions", { diff --git a/src/app/page.tsx b/src/app/page.tsx index 2c580ab..d406c74 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Card } from '@/components/ui/card' +import { RecurrencePicker } from '@/components/recurrence-picker' import { addEvent, deleteEvent, getAllEvents, clearEvents, getDB } from '@/lib/db' import { parseICS, generateICS } from '@/lib/ical' @@ -25,7 +26,7 @@ export default function HomePage() { const [start, setStart] = useState('') const [end, setEnd] = useState('') const [allDay, setAllDay] = useState(false) - const [recurrenceRule, setRecurrenceRule] = useState('') + const [recurrenceRule, setRecurrenceRule] = useState(undefined) // AI const [aiPrompt, setAiPrompt] = useState('') @@ -58,7 +59,7 @@ export default function HomePage() { description, location, url, - recurrenceRule: recurrenceRule || undefined, + recurrenceRule, start, end: end || undefined, allDay, @@ -155,7 +156,7 @@ export default function HomePage() { setAllDay(ev.allDay || false) setEditingId(null) setDialogOpen(true) - setRecurrenceRule(ev.recurrenceRule || '') + setRecurrenceRule(ev.recurrenceRule || undefined) } else { // Save them all directly to DB for (const ev of data) { @@ -308,11 +309,8 @@ export default function HomePage() { value={description} onChange={e => setDescription(e.target.value)} /> setLocation(e.target.value)} /> setUrl(e.target.value)} /> - setRecurrenceRule(e.target.value)} - /> + +