Chat search
Full-text search across chats with the matched words marked in titles and snippets, role filters, recent chats and full keyboard control.
On this page
Full-text search across chats with the matched words marked in titles and snippets.
Installation
Usage
import { ChatSearch, ChatSearchDialog } from "@/components/ui/chatcn/chat-search"
<ChatSearchDialog
open={open}
onOpenChange={setOpen}
chats={chats} // { id, title, updatedAt, messages: { id, role, text }[] }[]
assistantName="chatcn"
onOpen={(result, { newTab }) => router.push(`/c/${result.chatId}#${result.messageId}`)}
/>Local search needs every word of the query in one message or in the title, and returns one row per chat with its best message as a snippet. For large histories, search on the server: pass results, update them in onQueryChange (debounced by debounceMs) and set loading while a request is in flight. searchChats is exported if you want the same ranking elsewhere.
Examples
Dialog
The same search in a dialog near the top of the screen, on Cmd+Shift+F.
States
| State | Shows |
|---|---|
| Idle | Recent chats with their latest message |
| Searching | Previous results stay, dimmed; a spinner after 200 ms |
| Results | Title, snippet with marks, who said it, match count and date |
| No results | The query, and a way to search all messages when a filter is on |
| Error | What went wrong, how to fix it, and Retry search |
Props
| Prop | Type | Notes |
|---|---|---|
chats | ChatSearchChat[] | Searched locally |
results | ChatSearchResult[] | Server results. Turns local search off |
query, defaultQuery, onQueryChange | Controlled or uncontrolled | |
loading | boolean | Server request in flight |
error, onRetry | boolean | string, () => void | A string replaces the default message |
onOpen | (result, { newTab }) => void | Enter, click, Cmd/Ctrl+Enter or middle click |
assistantName | string | Snippet label and filter name. Default Assistant |
roleFilter, role, defaultRole, onRoleChange | All, Your messages, replies | |
recentLimit | number | Recent chats while idle. Default 6 |
now, locale | Reference time and locale for dates | |
debounceMs | number | Default 150 |
clearOnEscape | boolean | Escape clears before anything else. Off in the dialog |
Keyboard
| Key | Action |
|---|---|
↓, ↑ | Move through results |
Enter | Open the chat at the matching message |
Cmd+Enter, Ctrl+Enter | Open in a new tab |
←, → on the filters | Change the filter |
Esc | Clear the query, then close the dialog |
Accessibility
- A combobox with
aria-activedescendant: the field keeps focus while the highlight moves. - Matches are real
markelements, so they survive copy and forced colors. - Filters are a
radiogroupwith roving focus. The number of chats found is announced once results settle. - Errors use
role=alertand say how to fix the problem.
Motion
- Results replace each other without animation: they change on every keystroke.
- The spinner waits 200 ms, so local search never shows one.
- The dialog follows the dialog spec from a pointer and is instant from the keyboard.