add recurrence editor component

This commit is contained in:
2025-08-15 22:46:44 -04:00
parent 2d5db29f27
commit 836feb2e11
3 changed files with 149 additions and 18 deletions

View File

@@ -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", {

View File

@@ -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<string | undefined>(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)} />
<Input placeholder="Location" value={location} onChange={e => setLocation(e.target.value)} />
<Input placeholder="URL" value={url} onChange={e => setUrl(e.target.value)} />
<Input
placeholder="Recurrence rule (e.g. FREQ=WEEKLY;BYDAY=MO)"
value={recurrenceRule}
onChange={e => setRecurrenceRule(e.target.value)}
/>
<RecurrencePicker value={recurrenceRule} onChange={setRecurrenceRule} />
<label className="flex items-center gap-2 mt-2">
<input type="checkbox" checked={allDay} onChange={e => setAllDay(e.target.checked)} />
All day event