Semantic search
Search by meaning over the app's own records with a local vector index and hybrid keyword matching, showing why each result matched.
- Apple Foundation Models: supported
- Gemini Nano: not supported
- Llama (not yet supported): not supported
- A cloud model: supported
- Embeddings come from Apple (NLContextualEmbedding) or a cloud model. Without them it falls back to keyword search.
Install
npx ondai add semantic-searchUsage
import { SemanticSearch } from '@/components/ondai/semantic-search'
import { sqliteStorage } from '@ondai/core/expo'
import { openDatabaseAsync } from 'expo-sqlite'
<SemanticSearch
items={notes.map((n) => ({ id: n.id, text: n.body, metadata: n }))}
storage={sqliteStorage({ open: openDatabaseAsync })}
presentation="bar" resultStyle="list"
onSelect={(hit) => router.push(`/notes/${hit.id}`)}
/>- Embeddings come from Apple NLContextualEmbedding (512 dims, iOS 17+) or a cloud embedding model; without them, keyword search still works.
- Hybrid scoring blends cosine similarity and keyword overlap; "why it matched" shows the terms.
Customize
Every AI component takes the same look and customization props, so it fits your app rather than ours.
<SemanticSearch
variant="pop"
tint="#FF5A5F"
size="lg"
radius={12}
icons={{ search: Search, clear: null }}
labels={{ placeholder: '…', title: '…' }}
/>| Prop | What it does |
|---|---|
variant | The look of the whole component: clean, minimal, glass, pop, candy, metal, shine |
size | sm, md, lg; type, rows, icons and keys scale together |
tint | Any colour; the variant paints its keys, tiles and glow in it |
radius | Corner radius of the container |
icons | Replace any icon (a lucide component, an element, or null to hide it): search, clear, result, recent, empty, error |
labels | Every visible and spoken string; defaults are exported as semanticSearchLabels: placeholder, title, recent, searching, indexing, of, items, emptyTitle, emptyHint, matches, similar, result, results, byMeaning, keywordsOnly, other, clear, inputLabel |
| Slots | Draw a part yourself: renderIcon, renderItem, renderResult, renderEmpty, renderFooter |
styles | Style any named part |
Variants
| Prop | Options |
|---|---|
presentation | bar, spotlight, header |
variant | clean, minimal, glass, pop, candy, metal, shine |
resultStyle | list, cards, grouped |
size | sm, md, lg |
Token budget
Input budget: 512 tokens. Embeddings only (Apple NLContextualEmbedding, 512 dims); no LLM call. Keyword fallback without embeddings.
Fallback
When no provider is ready the component renders the unavailable state (AvailabilityGate copy) and, where a deterministic path exists, keeps working without the model.
Eval score
No model-driven fixtures for this component.
Files
Registry dependencies: model-download, on-device-badge, sheet, shimmer-text, surface, skeleton
npm dependencies: @ondai/core, @ondai/tokens, expo-haptics, lucide-react-native, react-native-reanimated
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.
Ask page
Ask about the page on screen. The answer streams in grounded in its text, with tap-to-jump quotes and follow-ups, and says when the page does not hold it.











