Text to event

A pasted or selected message becomes a calendar event: title, date, time, place and people fill in one by one with per-field confidence, stay editable, then go to the calendar.

Mock· scripted answersNo model runs in a browser, so every answer here is scripted. On a phone it comes from the model.
  • Apple Foundation Models: supported
  • Gemini Nano: supported
  • Llama (not yet supported): not supported
  • A cloud model: supported

Install

npx ondai add text-to-event

Usage

import { TextToEvent } from '@/components/ondai/text-to-event'
import * as Calendar from 'expo-calendar'

<TextToEvent
  onAdd={async (e) => {
    const end = new Date(e.start.getTime() + 60 * 60 * 1000)
    await Calendar.createEventAsync(calendarId, { title: e.title, startDate: e.start, endDate: end, allDay: e.allDay, location: e.place })
  }}
/>

For a chat or mail view, pass the selected text and use the message presentation:

<TextToEvent presentation="message" message={selectedText} autoRun onAdd={addToCalendar} />
  • The model only copies words out of the message ("next Friday", "8pm", "Nonna’s"). Dates and times are resolved on device against now, so "next Friday" never depends on the model knowing today's date.
  • Each field gets its own confidence. A place or a name the message never says is marked "check". Tap any row to change it.
  • An unclear date is highlighted with the days it could mean, and Add to calendar waits for a choice: "next Friday" said early in the week (this Friday or the one after), "Thursday or Friday", and numeric dates like 05/06 (pass dayFirst to read them day first).
  • The event handed to onAdd has title, date (YYYY-MM-DD), time (HH:mm, none means all day), place, people, a local start Date and allDay.
  • Headless: useTextToEvent() gives you run, draft, fields (confidence and candidates), set, choose and event. refineEvent() is the pure resolver.

Customize

Every AI component takes the same look and customization props, so it fits your app rather than ours.

<TextToEvent
  variant="pop"
  tint="#FF5A5F"
  size="lg"
  radius={12}
  clock="24h"
  icons={{ event: CalendarHeart, edit: null }}
  labels={{ find: '…', addToCalendar: '…' }}
  formatDate={(d) => d.toLocaleDateString('it-IT', { weekday: 'short', day: 'numeric', month: 'short' })}
/>
PropWhat it does
variantThe look of the whole component: clean, minimal, glass, pop, candy, metal, shine, bevel
sizesm, md, lg; type, rows, icons and keys scale together
tintAny colour; the variant paints its keys, tiles and the chosen date in it
radiusCorner radius of the box, the bubble and the card
clock12h (default) or 24h; or pass formatTime and formatDate
revealIntervalMs between fields filling in (default 240). 0, or reduced motion, fills them at once
editableLet rows be tapped and edited (default true)
now, dayFirstThe reference day for relative dates, and how to read 05/06
iconsReplace any icon (a lucide component, an element, or null to hide it): event, title, date, time, place, people, edit, added, again, retry, error, refusal
labelsEvery visible and spoken string; defaults are exported as textToEventLabels: heading, hint, placeholder, inputLabel, find, thinking, filling, found, checkDate, which, title, date, time, place, people, allDay, add, check, editHint, addToCalendar, added, again, noEvent, noEventHint, error, refusal, retry, preparing
SlotsDraw a part yourself: renderRow, renderAdd
stylesStyle any named part: container, bubble, card, row, rowLabel, rowValue, actions

States

StateWhat you see
idleThe message box (or bubble) and Find event
thinkingThe message is quoted and the card waits with quiet bars while the model reads
fillingFields arrive one at a time, with a light tick each
doneEvery field, editable, and Add to calendar
ambiguousThe date row is highlighted with the days it could mean
no-eventThe message names no event
errorWhat went wrong, and Try again
refusalThe model declined; no retry
unavailable, preparingThe availability copy, or the model download bar

Variants

PropOptions
variantclean, minimal, glass, pop, candy, metal, shine, bevel
presentationcard, message
sizesm, md, lg

Token budget

Input budget: 800 tokens. One structured call on the message; the model copies words, then dates, times and names are resolved and checked against the message on device.

Fallback

When no provider is ready the component shows the unavailable state (AvailabilityGate copy) or the download progress, and the find key waits. Dates and times are always resolved deterministically, so the same message gives the same day on every provider.

Files

Registry dependencies: button, chip, field, model-download, on-device-badge, shimmer-text, skeleton, surface

npm dependencies: @ondai/core, @ondai/tokens, expo-haptics, lucide-react-native, react-native-reanimated, zod

On this page