@rx-lab/dashboard-searching-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +704 -0
- package/dist/components/search/SearchAgent/SearchAgent.d.ts +103 -0
- package/dist/components/search/SearchAgent/SearchAgent.d.ts.map +1 -0
- package/dist/components/search/SearchAgent/components/FileResultCard.d.ts +39 -0
- package/dist/components/search/SearchAgent/components/FileResultCard.d.ts.map +1 -0
- package/dist/components/search/SearchAgent/components/MessageActions.d.ts +33 -0
- package/dist/components/search/SearchAgent/components/MessageActions.d.ts.map +1 -0
- package/dist/components/search/SearchAgent/components/MessageBubble.d.ts +54 -0
- package/dist/components/search/SearchAgent/components/MessageBubble.d.ts.map +1 -0
- package/dist/components/search/SearchAgent/components/StreamingIndicator.d.ts +23 -0
- package/dist/components/search/SearchAgent/components/StreamingIndicator.d.ts.map +1 -0
- package/dist/components/search/SearchAgent/components/index.d.ts +5 -0
- package/dist/components/search/SearchAgent/components/index.d.ts.map +1 -0
- package/dist/components/search/SearchAgent/index.d.ts +3 -0
- package/dist/components/search/SearchAgent/index.d.ts.map +1 -0
- package/dist/components/search/SearchCommand/SearchCommand.d.ts +99 -0
- package/dist/components/search/SearchCommand/SearchCommand.d.ts.map +1 -0
- package/dist/components/search/SearchCommand/index.d.ts +2 -0
- package/dist/components/search/SearchCommand/index.d.ts.map +1 -0
- package/dist/components/search/SearchTrigger/SearchTrigger.d.ts +60 -0
- package/dist/components/search/SearchTrigger/SearchTrigger.d.ts.map +1 -0
- package/dist/components/search/SearchTrigger/index.d.ts +2 -0
- package/dist/components/search/SearchTrigger/index.d.ts.map +1 -0
- package/dist/components/ui/badge.d.ts +10 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/command.d.ts +20 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/context-menu.d.ts +26 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/dialog.d.ts +16 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +6 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/tooltip.d.ts +8 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useSearchAgent.d.ts +90 -0
- package/dist/hooks/useSearchAgent.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/package.json +42 -0
- package/dist/searching-ui.js +16278 -0
- package/dist/searching-ui.js.map +1 -0
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/package.json +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
# searching-ui
|
|
2
|
+
|
|
3
|
+
A React component library for building AI-powered search interfaces with streaming chat support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **SearchTrigger** - Customizable search trigger button
|
|
8
|
+
- **SearchCommand** - Command palette dialog with instant search results
|
|
9
|
+
- **SearchAgent** - AI-powered chat interface with streaming responses
|
|
10
|
+
- **useSearchAgent** - React hook for managing chat state
|
|
11
|
+
- Full TypeScript support
|
|
12
|
+
- Tailwind CSS v4 styling
|
|
13
|
+
- shadcn/ui components included
|
|
14
|
+
- Framer Motion animations
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install searching-ui
|
|
20
|
+
# or
|
|
21
|
+
bun add searching-ui
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Peer Dependencies
|
|
25
|
+
|
|
26
|
+
If you plan to use the AI agent features, you'll also need:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @ai-sdk/react ai
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Basic Search Command
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { useState } from "react";
|
|
38
|
+
import { SearchTrigger, SearchCommand } from "searching-ui";
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
const [open, setOpen] = useState(false);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<SearchTrigger onClick={() => setOpen(true)} />
|
|
46
|
+
|
|
47
|
+
<SearchCommand
|
|
48
|
+
open={open}
|
|
49
|
+
onOpenChange={setOpen}
|
|
50
|
+
onSearch={async ({ query, limit }) => {
|
|
51
|
+
// Your search implementation
|
|
52
|
+
const results = await fetch(`/api/search?q=${query}&limit=${limit}`);
|
|
53
|
+
return results.json();
|
|
54
|
+
}}
|
|
55
|
+
onResultSelect={(result) => {
|
|
56
|
+
// Handle result selection
|
|
57
|
+
console.log("Selected:", result);
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
</>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### With AI Agent Mode
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { useState } from "react";
|
|
69
|
+
import { SearchTrigger, SearchCommand } from "searching-ui";
|
|
70
|
+
|
|
71
|
+
function App() {
|
|
72
|
+
const [open, setOpen] = useState(false);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<SearchTrigger onClick={() => setOpen(true)} />
|
|
77
|
+
|
|
78
|
+
<SearchCommand
|
|
79
|
+
open={open}
|
|
80
|
+
onOpenChange={setOpen}
|
|
81
|
+
onSearch={async ({ query }) => {
|
|
82
|
+
const results = await fetch(`/api/search?q=${query}`);
|
|
83
|
+
return results.json();
|
|
84
|
+
}}
|
|
85
|
+
enableAgentMode
|
|
86
|
+
agentConfig={{
|
|
87
|
+
apiEndpoint: "/api/search-agent",
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Components
|
|
96
|
+
|
|
97
|
+
### SearchTrigger
|
|
98
|
+
|
|
99
|
+
A trigger button for opening the search dialog.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { SearchTrigger } from "searching-ui";
|
|
103
|
+
|
|
104
|
+
// Basic usage
|
|
105
|
+
<SearchTrigger onClick={() => setOpen(true)} />
|
|
106
|
+
|
|
107
|
+
// Custom placeholder
|
|
108
|
+
<SearchTrigger
|
|
109
|
+
onClick={() => setOpen(true)}
|
|
110
|
+
placeholder="Find documents..."
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
// Custom shortcut
|
|
114
|
+
<SearchTrigger
|
|
115
|
+
onClick={() => setOpen(true)}
|
|
116
|
+
shortcut={{ key: "P", modifier: "Ctrl" }}
|
|
117
|
+
/>
|
|
118
|
+
|
|
119
|
+
// Completely custom content
|
|
120
|
+
<SearchTrigger onClick={() => setOpen(true)}>
|
|
121
|
+
<span>Custom Search Button</span>
|
|
122
|
+
</SearchTrigger>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Props
|
|
126
|
+
|
|
127
|
+
| Prop | Type | Default | Description |
|
|
128
|
+
|------|------|---------|-------------|
|
|
129
|
+
| `onClick` | `() => void` | required | Click handler to open search |
|
|
130
|
+
| `placeholder` | `string` | `"Search files..."` | Placeholder text |
|
|
131
|
+
| `shortcut` | `ShortcutConfig \| null` | `{ key: "K", modifier: "⌘" }` | Keyboard shortcut display |
|
|
132
|
+
| `icon` | `ComponentType` | `Search` | Icon component |
|
|
133
|
+
| `children` | `ReactNode` | - | Custom content (replaces default) |
|
|
134
|
+
| `variant` | `string` | `"outline"` | Button variant |
|
|
135
|
+
| `className` | `string` | - | Additional class name |
|
|
136
|
+
|
|
137
|
+
### SearchCommand
|
|
138
|
+
|
|
139
|
+
A search command dialog with optional AI agent mode.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { SearchCommand } from "searching-ui";
|
|
143
|
+
|
|
144
|
+
<SearchCommand
|
|
145
|
+
open={open}
|
|
146
|
+
onOpenChange={setOpen}
|
|
147
|
+
onSearch={async ({ query, searchType, limit }) => {
|
|
148
|
+
const results = await searchAPI(query, { type: searchType, limit });
|
|
149
|
+
return results;
|
|
150
|
+
}}
|
|
151
|
+
onResultSelect={(result) => {
|
|
152
|
+
router.push(`/files/${result.id}`);
|
|
153
|
+
}}
|
|
154
|
+
searchTypes={[
|
|
155
|
+
{ id: "all", label: "All" },
|
|
156
|
+
{ id: "documents", label: "Documents" },
|
|
157
|
+
{ id: "images", label: "Images" },
|
|
158
|
+
]}
|
|
159
|
+
renderResult={(result, onSelect) => (
|
|
160
|
+
<div onClick={onSelect} className="p-2 cursor-pointer hover:bg-accent">
|
|
161
|
+
<h3>{result.title}</h3>
|
|
162
|
+
<p>{result.snippet}</p>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
enableAgentMode
|
|
166
|
+
agentConfig={{
|
|
167
|
+
apiEndpoint: "/api/search-agent",
|
|
168
|
+
header: { title: "AI Assistant" },
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### Props
|
|
174
|
+
|
|
175
|
+
| Prop | Type | Default | Description |
|
|
176
|
+
|------|------|---------|-------------|
|
|
177
|
+
| `open` | `boolean` | required | Whether dialog is open |
|
|
178
|
+
| `onOpenChange` | `(open: boolean) => void` | required | Open state handler |
|
|
179
|
+
| `onSearch` | `(params: SearchParams) => Promise<TResult[]>` | required | Search function |
|
|
180
|
+
| `onResultSelect` | `(result: TResult) => void` | - | Result selection handler |
|
|
181
|
+
| `searchTypes` | `SearchTypeConfig[]` | - | Available search types |
|
|
182
|
+
| `debounceMs` | `number` | `300` | Debounce delay |
|
|
183
|
+
| `limit` | `number` | `10` | Max results |
|
|
184
|
+
| `renderResult` | `(result, onSelect) => ReactNode` | - | Custom result renderer |
|
|
185
|
+
| `renderEmpty` | `(query, hasResults) => ReactNode` | - | Custom empty state |
|
|
186
|
+
| `renderLoading` | `() => ReactNode` | - | Custom loading state |
|
|
187
|
+
| `enableAgentMode` | `boolean` | `false` | Enable AI agent mode |
|
|
188
|
+
| `agentConfig` | `Partial<SearchAgentProps>` | - | Agent configuration |
|
|
189
|
+
| `placeholder` | `string` | `"Search..."` | Input placeholder |
|
|
190
|
+
|
|
191
|
+
#### Customizing the AI Loading Indicator
|
|
192
|
+
|
|
193
|
+
When using agent mode, you can customize the loading indicator shown while the AI is processing:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { Brain } from "lucide-react";
|
|
197
|
+
|
|
198
|
+
<SearchCommand
|
|
199
|
+
open={open}
|
|
200
|
+
onOpenChange={setOpen}
|
|
201
|
+
onSearch={onSearch}
|
|
202
|
+
enableAgentMode
|
|
203
|
+
agentConfig={{
|
|
204
|
+
apiEndpoint: "/api/search-agent",
|
|
205
|
+
input: {
|
|
206
|
+
streamingText: "Thinking...",
|
|
207
|
+
streamingIcon: Brain,
|
|
208
|
+
},
|
|
209
|
+
}}
|
|
210
|
+
/>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### SearchAgent
|
|
214
|
+
|
|
215
|
+
An AI-powered search agent component with streaming chat support.
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { SearchAgent } from "searching-ui";
|
|
219
|
+
|
|
220
|
+
<SearchAgent
|
|
221
|
+
initialQuery="Find all PDF documents"
|
|
222
|
+
apiEndpoint="/api/search-agent"
|
|
223
|
+
onBack={() => setMode("quick")}
|
|
224
|
+
onClose={() => setOpen(false)}
|
|
225
|
+
header={{
|
|
226
|
+
title: "AI Search Assistant",
|
|
227
|
+
showBackButton: true,
|
|
228
|
+
showClearButton: true,
|
|
229
|
+
}}
|
|
230
|
+
toolResultRenderers={{
|
|
231
|
+
display_files: ({ output, onAction }) => (
|
|
232
|
+
<div>
|
|
233
|
+
{output.files.map((file) => (
|
|
234
|
+
<FileCard
|
|
235
|
+
key={file.id}
|
|
236
|
+
file={file}
|
|
237
|
+
onClick={() => onAction({ type: "navigate", payload: `/files/${file.id}` })}
|
|
238
|
+
/>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
),
|
|
242
|
+
}}
|
|
243
|
+
renderAssistantContent={(content) => (
|
|
244
|
+
<CustomMarkdown>{content}</CustomMarkdown>
|
|
245
|
+
)}
|
|
246
|
+
/>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Props
|
|
250
|
+
|
|
251
|
+
| Prop | Type | Default | Description |
|
|
252
|
+
|------|------|---------|-------------|
|
|
253
|
+
| `initialQuery` | `string` | `""` | Initial query to send |
|
|
254
|
+
| `initialMessages` | `UIMessage[]` | - | Initial messages |
|
|
255
|
+
| `apiEndpoint` | `string` | `"/api/search-agent"` | Chat API endpoint |
|
|
256
|
+
| `chatInstance` | `Chat<UIMessage>` | - | Custom Chat instance |
|
|
257
|
+
| `toolResultRenderers` | `ToolResultRenderers` | `{}` | Custom tool renderers |
|
|
258
|
+
| `onToolAction` | `(action: ToolAction) => void` | - | Tool action handler |
|
|
259
|
+
| `renderMessage` | `(props) => ReactNode` | - | Custom message renderer |
|
|
260
|
+
| `renderUserContent` | `(content: string) => ReactNode` | - | Custom user content |
|
|
261
|
+
| `renderAssistantContent` | `(content: string) => ReactNode` | - | Custom assistant content |
|
|
262
|
+
| `renderStreamingIndicator` | `() => ReactNode` | - | Custom loading indicator |
|
|
263
|
+
| `onNavigate` | `(path: string) => void` | - | Navigation handler |
|
|
264
|
+
| `onBack` | `() => void` | - | Back button handler |
|
|
265
|
+
| `onClose` | `() => void` | - | Close handler |
|
|
266
|
+
| `onClearHistory` | `() => void` | - | Clear history handler |
|
|
267
|
+
| `header` | `SearchAgentHeaderConfig` | - | Header configuration |
|
|
268
|
+
| `input` | `SearchAgentInputConfig` | - | Input configuration |
|
|
269
|
+
| `enableMessageActions` | `boolean` | `true` | Show action buttons on messages (Copy, Regenerate) |
|
|
270
|
+
| `onMessageRegenerate` | `(messageId, content) => void` | - | Called when user regenerates a message |
|
|
271
|
+
|
|
272
|
+
#### Message Actions
|
|
273
|
+
|
|
274
|
+
By default, action buttons appear when hovering over messages. The buttons are:
|
|
275
|
+
|
|
276
|
+
| Action | Available On | Description |
|
|
277
|
+
|--------|-------------|-------------|
|
|
278
|
+
| **Copy** | All messages | Copy message content to clipboard |
|
|
279
|
+
| **Regenerate** | All messages | For user messages: removes this and all subsequent messages, then resends. For assistant messages: regenerates this response |
|
|
280
|
+
|
|
281
|
+
To disable the action buttons:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
<SearchAgent
|
|
285
|
+
apiEndpoint="/api/search-agent"
|
|
286
|
+
enableMessageActions={false}
|
|
287
|
+
/>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
To handle regenerate events:
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
<SearchAgent
|
|
294
|
+
apiEndpoint="/api/search-agent"
|
|
295
|
+
onMessageRegenerate={(messageId, content) => {
|
|
296
|
+
console.log("Regenerating:", messageId, content);
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### Input Configuration
|
|
302
|
+
|
|
303
|
+
The `input` prop accepts an `SearchAgentInputConfig` object:
|
|
304
|
+
|
|
305
|
+
| Option | Type | Default | Description |
|
|
306
|
+
|--------|------|---------|-------------|
|
|
307
|
+
| `placeholder` | `string` | `"Ask a follow-up question..."` | Input placeholder when ready |
|
|
308
|
+
| `placeholderProcessing` | `string` | `"Generating..."` | Input placeholder when processing |
|
|
309
|
+
| `streamingText` | `ReactNode` | `"Searching..."` | Text or component shown in loading indicator |
|
|
310
|
+
| `streamingIcon` | `ComponentType \| null` | `Loader2` | Custom spinner icon, or `null` to hide |
|
|
311
|
+
|
|
312
|
+
**Example with custom streaming indicator:**
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { Brain } from "lucide-react";
|
|
316
|
+
|
|
317
|
+
<SearchAgent
|
|
318
|
+
apiEndpoint="/api/search-agent"
|
|
319
|
+
input={{
|
|
320
|
+
placeholder: "Ask me anything...",
|
|
321
|
+
streamingText: "Thinking...",
|
|
322
|
+
streamingIcon: Brain,
|
|
323
|
+
}}
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Example with fully custom content:**
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
<SearchAgent
|
|
331
|
+
apiEndpoint="/api/search-agent"
|
|
332
|
+
input={{
|
|
333
|
+
streamingText: (
|
|
334
|
+
<span className="flex items-center gap-1">
|
|
335
|
+
<Brain className="h-3 w-3 animate-pulse" />
|
|
336
|
+
Analyzing your request...
|
|
337
|
+
</span>
|
|
338
|
+
),
|
|
339
|
+
streamingIcon: null, // Hide default spinner since we include our own
|
|
340
|
+
}}
|
|
341
|
+
/>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Hooks
|
|
345
|
+
|
|
346
|
+
### useSearchAgent
|
|
347
|
+
|
|
348
|
+
A hook for managing AI chat state with the Vercel AI SDK.
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { useSearchAgent } from "searching-ui";
|
|
352
|
+
|
|
353
|
+
function CustomChatInterface() {
|
|
354
|
+
const {
|
|
355
|
+
messages,
|
|
356
|
+
isProcessing,
|
|
357
|
+
sendMessage,
|
|
358
|
+
stop,
|
|
359
|
+
clearMessages,
|
|
360
|
+
getTextContent,
|
|
361
|
+
hasContent,
|
|
362
|
+
} = useSearchAgent({
|
|
363
|
+
apiEndpoint: "/api/search-agent",
|
|
364
|
+
onMessagesChange: (messages) => {
|
|
365
|
+
// Persist messages
|
|
366
|
+
sessionStorage.setItem("chat", JSON.stringify(messages));
|
|
367
|
+
},
|
|
368
|
+
onToolCall: ({ toolName, args }) => {
|
|
369
|
+
console.log("Tool called:", toolName, args);
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<div>
|
|
375
|
+
{messages.map((message) => (
|
|
376
|
+
<div key={message.id}>
|
|
377
|
+
{getTextContent(message)}
|
|
378
|
+
</div>
|
|
379
|
+
))}
|
|
380
|
+
|
|
381
|
+
<button onClick={() => sendMessage("Hello!")}>
|
|
382
|
+
Send
|
|
383
|
+
</button>
|
|
384
|
+
|
|
385
|
+
{isProcessing && (
|
|
386
|
+
<button onClick={stop}>Stop</button>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### Options
|
|
394
|
+
|
|
395
|
+
| Option | Type | Description |
|
|
396
|
+
|--------|------|-------------|
|
|
397
|
+
| `apiEndpoint` | `string` | API endpoint for chat |
|
|
398
|
+
| `initialMessages` | `UIMessage[]` | Initial messages |
|
|
399
|
+
| `chatInstance` | `Chat<UIMessage>` | Custom Chat instance |
|
|
400
|
+
| `onMessagesChange` | `(messages) => void` | Messages change callback |
|
|
401
|
+
| `initialQuery` | `string` | Auto-send query on mount |
|
|
402
|
+
| `onToolCall` | `(toolCall) => void` | Tool call callback |
|
|
403
|
+
| `onFinish` | `(message) => void` | Completion callback |
|
|
404
|
+
| `onError` | `(error) => void` | Error callback |
|
|
405
|
+
|
|
406
|
+
#### Return Value
|
|
407
|
+
|
|
408
|
+
| Property | Type | Description |
|
|
409
|
+
|----------|------|-------------|
|
|
410
|
+
| `messages` | `UIMessage[]` | Current messages |
|
|
411
|
+
| `status` | `string` | Current status |
|
|
412
|
+
| `isProcessing` | `boolean` | Whether processing |
|
|
413
|
+
| `error` | `Error \| undefined` | Current error |
|
|
414
|
+
| `sendMessage` | `(text: string) => void` | Send a message |
|
|
415
|
+
| `stop` | `() => void` | Stop generation |
|
|
416
|
+
| `clearMessages` | `() => void` | Clear messages |
|
|
417
|
+
| `setMessages` | `function` | Set messages directly |
|
|
418
|
+
| `chat` | `Chat<UIMessage>` | Underlying Chat instance |
|
|
419
|
+
| `getTextContent` | `(message) => string` | Extract text content |
|
|
420
|
+
| `getToolCalls` | `(message) => ToolCallInfo[]` | Extract tool calls |
|
|
421
|
+
| `hasContent` | `(message) => boolean` | Check if has content |
|
|
422
|
+
|
|
423
|
+
## Sub-components
|
|
424
|
+
|
|
425
|
+
### MessageBubble
|
|
426
|
+
|
|
427
|
+
Renders a single chat message with support for tool results.
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
import { MessageBubble } from "searching-ui";
|
|
431
|
+
|
|
432
|
+
<MessageBubble
|
|
433
|
+
message={message}
|
|
434
|
+
toolResultRenderers={{
|
|
435
|
+
search_files: MyFileResultRenderer,
|
|
436
|
+
}}
|
|
437
|
+
renderUserContent={(content) => <span>{content}</span>}
|
|
438
|
+
renderAssistantContent={(content) => <Markdown>{content}</Markdown>}
|
|
439
|
+
/>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### StreamingIndicator
|
|
443
|
+
|
|
444
|
+
Shows a loading indicator while the AI is generating.
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
import { StreamingIndicator } from "searching-ui";
|
|
448
|
+
|
|
449
|
+
// Basic usage
|
|
450
|
+
<StreamingIndicator text="Thinking..." />
|
|
451
|
+
|
|
452
|
+
// Custom icons
|
|
453
|
+
<StreamingIndicator
|
|
454
|
+
text="Processing..."
|
|
455
|
+
botIcon={CustomBotIcon}
|
|
456
|
+
loadingIcon={CustomSpinner}
|
|
457
|
+
/>
|
|
458
|
+
|
|
459
|
+
// Hide the spinner (when text includes its own)
|
|
460
|
+
<StreamingIndicator
|
|
461
|
+
text={
|
|
462
|
+
<span className="flex items-center gap-1">
|
|
463
|
+
<Brain className="h-3 w-3 animate-pulse" />
|
|
464
|
+
Analyzing...
|
|
465
|
+
</span>
|
|
466
|
+
}
|
|
467
|
+
loadingIcon={null}
|
|
468
|
+
/>
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### Props
|
|
472
|
+
|
|
473
|
+
| Prop | Type | Default | Description |
|
|
474
|
+
|------|------|---------|-------------|
|
|
475
|
+
| `text` | `ReactNode` | `"Searching..."` | Text or component to display |
|
|
476
|
+
| `botIcon` | `ComponentType` | `Bot` | Bot avatar icon |
|
|
477
|
+
| `loadingIcon` | `ComponentType \| null` | `Loader2` | Spinner icon, or `null` to hide |
|
|
478
|
+
| `className` | `string` | - | Additional class name |
|
|
479
|
+
|
|
480
|
+
### FileResultCard
|
|
481
|
+
|
|
482
|
+
A card component for displaying file results.
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
import { FileResultCard } from "searching-ui";
|
|
486
|
+
|
|
487
|
+
<FileResultCard
|
|
488
|
+
file={{
|
|
489
|
+
id: "123",
|
|
490
|
+
title: "Document.pdf",
|
|
491
|
+
fileType: "document",
|
|
492
|
+
mimeType: "application/pdf",
|
|
493
|
+
folderName: "My Folder",
|
|
494
|
+
}}
|
|
495
|
+
description="A sample document"
|
|
496
|
+
onClick={() => navigate(`/files/123`)}
|
|
497
|
+
renderIcon={(fileType, mimeType) => (
|
|
498
|
+
<FileIcon type={fileType} />
|
|
499
|
+
)}
|
|
500
|
+
/>
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Custom Rendering
|
|
504
|
+
|
|
505
|
+
The library provides flexible customization options for rendering messages and tool results.
|
|
506
|
+
|
|
507
|
+
### Custom User Message Rendering
|
|
508
|
+
|
|
509
|
+
Use `renderUserContent` to customize how user messages are displayed:
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
<SearchAgent
|
|
513
|
+
apiEndpoint="/api/search-agent"
|
|
514
|
+
renderUserContent={(content) => (
|
|
515
|
+
<div className="font-medium text-blue-600">
|
|
516
|
+
{content}
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
519
|
+
/>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Custom Assistant Message Rendering
|
|
523
|
+
|
|
524
|
+
Use `renderAssistantContent` to customize assistant responses (e.g., with a custom Markdown renderer):
|
|
525
|
+
|
|
526
|
+
```tsx
|
|
527
|
+
import ReactMarkdown from 'react-markdown'
|
|
528
|
+
import remarkGfm from 'remark-gfm'
|
|
529
|
+
|
|
530
|
+
<SearchAgent
|
|
531
|
+
apiEndpoint="/api/search-agent"
|
|
532
|
+
renderAssistantContent={(content) => (
|
|
533
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
534
|
+
{content}
|
|
535
|
+
</ReactMarkdown>
|
|
536
|
+
)}
|
|
537
|
+
/>
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Custom Tool Result Rendering
|
|
541
|
+
|
|
542
|
+
Use `toolResultRenderers` to provide custom renderers for specific tool outputs. Each renderer receives the tool's output and an action handler:
|
|
543
|
+
|
|
544
|
+
```tsx
|
|
545
|
+
import { SearchAgent, ToolResultRendererProps, FileResultCard } from 'searching-ui'
|
|
546
|
+
|
|
547
|
+
// Custom renderer for file search results
|
|
548
|
+
const FileResultsRenderer = ({ output, onAction }: ToolResultRendererProps) => {
|
|
549
|
+
const files = output as Array<{ id: string; title: string; fileType: string }>
|
|
550
|
+
|
|
551
|
+
return (
|
|
552
|
+
<div className="grid gap-2">
|
|
553
|
+
{files.map((file) => (
|
|
554
|
+
<FileResultCard
|
|
555
|
+
key={file.id}
|
|
556
|
+
file={file}
|
|
557
|
+
onClick={() => onAction?.({ type: 'open', payload: file })}
|
|
558
|
+
/>
|
|
559
|
+
))}
|
|
560
|
+
</div>
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Custom renderer for a different tool
|
|
565
|
+
const WeatherRenderer = ({ output }: ToolResultRendererProps) => {
|
|
566
|
+
const data = output as { temp: number; condition: string }
|
|
567
|
+
return (
|
|
568
|
+
<div className="p-3 rounded-lg bg-blue-50">
|
|
569
|
+
<p>{data.temp}°F - {data.condition}</p>
|
|
570
|
+
</div>
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
<SearchAgent
|
|
575
|
+
apiEndpoint="/api/search-agent"
|
|
576
|
+
toolResultRenderers={{
|
|
577
|
+
display_files: FileResultsRenderer,
|
|
578
|
+
get_weather: WeatherRenderer,
|
|
579
|
+
}}
|
|
580
|
+
onToolAction={(action) => {
|
|
581
|
+
if (action.type === 'open') {
|
|
582
|
+
router.push(`/files/${action.payload.id}`)
|
|
583
|
+
}
|
|
584
|
+
}}
|
|
585
|
+
/>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### ToolResultRendererProps
|
|
589
|
+
|
|
590
|
+
| Prop | Type | Description |
|
|
591
|
+
|------|------|-------------|
|
|
592
|
+
| `output` | `unknown` | The tool's output data |
|
|
593
|
+
| `toolCallId` | `string` | Unique identifier for the tool call |
|
|
594
|
+
| `onAction` | `(action: ToolAction) => void` | Trigger actions to parent |
|
|
595
|
+
|
|
596
|
+
#### ToolAction
|
|
597
|
+
|
|
598
|
+
```tsx
|
|
599
|
+
interface ToolAction {
|
|
600
|
+
type: string // Action type (e.g., 'open', 'download', 'select')
|
|
601
|
+
payload: unknown // Action data
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Complete Message Override
|
|
606
|
+
|
|
607
|
+
For full control over message rendering, use `renderMessage` to override the entire message bubble:
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
import { UIMessage } from 'searching-ui'
|
|
611
|
+
|
|
612
|
+
<SearchAgent
|
|
613
|
+
apiEndpoint="/api/search-agent"
|
|
614
|
+
renderMessage={({ message, getTextContent, getToolCalls }) => {
|
|
615
|
+
const content = getTextContent(message)
|
|
616
|
+
const toolCalls = getToolCalls(message)
|
|
617
|
+
|
|
618
|
+
return (
|
|
619
|
+
<div className={message.role === 'user' ? 'text-right' : 'text-left'}>
|
|
620
|
+
<div className="inline-block p-3 rounded-lg">
|
|
621
|
+
<p>{content}</p>
|
|
622
|
+
{toolCalls.map((tool) => (
|
|
623
|
+
<div key={tool.toolCallId}>
|
|
624
|
+
Tool: {tool.toolName}
|
|
625
|
+
</div>
|
|
626
|
+
))}
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
)
|
|
630
|
+
}}
|
|
631
|
+
/>
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Custom Icons
|
|
635
|
+
|
|
636
|
+
Customize user and bot avatar icons in `MessageBubble`:
|
|
637
|
+
|
|
638
|
+
```tsx
|
|
639
|
+
import { User, Sparkles } from 'lucide-react'
|
|
640
|
+
|
|
641
|
+
<MessageBubble
|
|
642
|
+
message={message}
|
|
643
|
+
userIcon={User}
|
|
644
|
+
botIcon={Sparkles}
|
|
645
|
+
/>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Using with SearchCommand
|
|
649
|
+
|
|
650
|
+
When using `SearchCommand` with agent mode, pass custom renderers through `agentConfig`:
|
|
651
|
+
|
|
652
|
+
```tsx
|
|
653
|
+
<SearchCommand
|
|
654
|
+
open={open}
|
|
655
|
+
onOpenChange={setOpen}
|
|
656
|
+
onSearch={onSearch}
|
|
657
|
+
enableAgentMode
|
|
658
|
+
agentConfig={{
|
|
659
|
+
apiEndpoint: '/api/search-agent',
|
|
660
|
+
renderUserContent: (content) => <strong>{content}</strong>,
|
|
661
|
+
renderAssistantContent: (content) => <Markdown>{content}</Markdown>,
|
|
662
|
+
toolResultRenderers: {
|
|
663
|
+
display_files: FileResultsRenderer,
|
|
664
|
+
},
|
|
665
|
+
onToolAction: handleToolAction,
|
|
666
|
+
}}
|
|
667
|
+
/>
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Styling
|
|
671
|
+
|
|
672
|
+
The library uses Tailwind CSS v4 and includes shadcn/ui components. Styles are automatically injected when importing components.
|
|
673
|
+
|
|
674
|
+
To customize the theme, add CSS variables to your root:
|
|
675
|
+
|
|
676
|
+
```css
|
|
677
|
+
:root {
|
|
678
|
+
--background: 0 0% 100%;
|
|
679
|
+
--foreground: 0 0% 3.9%;
|
|
680
|
+
--primary: 0 0% 9%;
|
|
681
|
+
--primary-foreground: 0 0% 98%;
|
|
682
|
+
/* ... other variables */
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
## Development
|
|
687
|
+
|
|
688
|
+
```bash
|
|
689
|
+
# Install dependencies
|
|
690
|
+
bun install
|
|
691
|
+
|
|
692
|
+
# Run tests
|
|
693
|
+
bun run test
|
|
694
|
+
|
|
695
|
+
# Build library
|
|
696
|
+
bun run build
|
|
697
|
+
|
|
698
|
+
# Run example app
|
|
699
|
+
bun run example:dev
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
## License
|
|
703
|
+
|
|
704
|
+
MIT
|