@timbal-ai/timbal-react 0.2.2 → 0.3.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 +326 -44
- package/dist/index.cjs +2619 -417
- package/dist/index.d.cts +820 -17
- package/dist/index.d.ts +820 -17
- package/dist/index.esm.js +2646 -471
- package/dist/styles.css +210 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -29,6 +29,17 @@ The package ships pre-built Tailwind class names. Add this `@source` line to you
|
|
|
29
29
|
|
|
30
30
|
> Adjust the path if your CSS file lives at a different depth relative to `node_modules`.
|
|
31
31
|
|
|
32
|
+
### Companion stylesheet (optional)
|
|
33
|
+
|
|
34
|
+
For blueprint-style polish (welcome animations, suggestion chips, tool indicators), import the shipped companion CSS once. Requires Tailwind v4 design tokens (`--primary`, etc.) in your app:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// src/main.tsx
|
|
38
|
+
import "@timbal-ai/timbal-react/styles.css";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can omit this entirely and style components yourself — the package does not bundle mandatory CSS beyond what Tailwind scans from `dist/`.
|
|
42
|
+
|
|
32
43
|
### CSS imports
|
|
33
44
|
|
|
34
45
|
Import these stylesheets once in your app entry:
|
|
@@ -78,6 +89,20 @@ export default function App() {
|
|
|
78
89
|
/>
|
|
79
90
|
```
|
|
80
91
|
|
|
92
|
+
Suggestions also accept a function (sync or async) for per-user or server-driven chips:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<TimbalChat
|
|
96
|
+
workforceId="your-workforce-id"
|
|
97
|
+
suggestions={async () => {
|
|
98
|
+
const res = await authFetch("/api/suggestions");
|
|
99
|
+
return res.json(); // ThreadSuggestion[]
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each chip supports `icon`, `description`, and `prompt` (sent instead of `title` when clicked).
|
|
105
|
+
|
|
81
106
|
### Placeholder and width
|
|
82
107
|
|
|
83
108
|
```tsx
|
|
@@ -104,6 +129,66 @@ const [workforceId, setWorkforceId] = useState("agent-a");
|
|
|
104
129
|
<TimbalChat workforceId={workforceId} key={workforceId} />
|
|
105
130
|
```
|
|
106
131
|
|
|
132
|
+
### Drop-in shell (header + agent picker)
|
|
133
|
+
|
|
134
|
+
`TimbalChatShell` wraps the common blueprint layout: brand area, workforce selector, optional header actions, and a full-height chat. When `workforceId` is omitted, it fetches `{baseUrl}/workforce` and selects the first agent automatically:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { TimbalChatShell, Button, useSession } from "@timbal-ai/timbal-react";
|
|
138
|
+
|
|
139
|
+
export default function App() {
|
|
140
|
+
const { logout, isAuthenticated } = useSession();
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<TimbalChatShell
|
|
144
|
+
brand={<span className="font-semibold">Acme AI</span>}
|
|
145
|
+
headerActions={
|
|
146
|
+
isAuthenticated ? (
|
|
147
|
+
<Button variant="ghost" size="sm" onClick={logout}>
|
|
148
|
+
Log out
|
|
149
|
+
</Button>
|
|
150
|
+
) : null
|
|
151
|
+
}
|
|
152
|
+
welcome={{ heading: "How can I help you today?" }}
|
|
153
|
+
suggestions={[{ title: "Get started" }]}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Pass `workforceId` to lock the agent and hide the built-in selector. Use `hideWorkforceSelector` when you render your own picker.
|
|
160
|
+
|
|
161
|
+
### Workforce list hook
|
|
162
|
+
|
|
163
|
+
For custom layouts (sidebar tree, command palette), use `useWorkforces` with the optional `WorkforceSelector`:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
import {
|
|
167
|
+
TimbalChat,
|
|
168
|
+
useWorkforces,
|
|
169
|
+
WorkforceSelector,
|
|
170
|
+
} from "@timbal-ai/timbal-react";
|
|
171
|
+
|
|
172
|
+
function ChatWithPicker() {
|
|
173
|
+
const { workforces, selectedId, setSelectedId, isLoading } = useWorkforces();
|
|
174
|
+
|
|
175
|
+
if (isLoading) return <div>Loading agents…</div>;
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div className="flex h-screen flex-col">
|
|
179
|
+
<WorkforceSelector
|
|
180
|
+
workforces={workforces}
|
|
181
|
+
value={selectedId}
|
|
182
|
+
onChange={setSelectedId}
|
|
183
|
+
/>
|
|
184
|
+
<TimbalChat workforceId={selectedId} key={selectedId} className="min-h-0 flex-1" />
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`useWorkforces` accepts `baseUrl`, `fetch`, and `pickInitial` (custom resolver for the default selection). It returns `selected`, `error`, and `refresh()` as well.
|
|
191
|
+
|
|
107
192
|
---
|
|
108
193
|
|
|
109
194
|
## Splitting the runtime and UI
|
|
@@ -138,6 +223,52 @@ Useful when your API is mounted at a subpath (e.g. behind a reverse proxy):
|
|
|
138
223
|
</TimbalRuntimeProvider>
|
|
139
224
|
```
|
|
140
225
|
|
|
226
|
+
### Attachments
|
|
227
|
+
|
|
228
|
+
Attachments are **opt-in**. Pass `attachments` to enable the composer `+` button, drag-and-drop, and multimodal prompts:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
<TimbalChat workforceId="your-workforce-id" attachments />
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
When enabled, each file is uploaded via `POST` to `${baseUrl}/files/upload` (multipart `file` field). The response must include `{ url }` (or `{ signed_url }` / `{ id }`). That URL is sent to the workforce as `{ type: "file", file: "<url>" }` alongside `{ type: "text", text: "..." }` when the user typed a message.
|
|
235
|
+
|
|
236
|
+
Your API must expose that upload route (the Timbal blueprint API includes it). `authFetch` is used by default and must **not** force a `Content-Type` header on `FormData` uploads.
|
|
237
|
+
|
|
238
|
+
#### Variants
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
// Default upload adapter
|
|
242
|
+
<TimbalChat workforceId="..." attachments />
|
|
243
|
+
|
|
244
|
+
// Custom endpoint or MIME whitelist
|
|
245
|
+
<TimbalChat
|
|
246
|
+
workforceId="..."
|
|
247
|
+
attachments={{ uploadUrl: "/api/uploads", accept: "image/*,application/pdf" }}
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
// Fully custom adapter (e.g. presigned S3)
|
|
251
|
+
<TimbalChat workforceId="..." attachments={myAdapter} />
|
|
252
|
+
|
|
253
|
+
// Explicitly off (default when prop is omitted)
|
|
254
|
+
<TimbalChat workforceId="..." attachments={null} />
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### Power-user exports
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
import {
|
|
261
|
+
createDefaultAttachmentAdapter,
|
|
262
|
+
createUploadAttachmentAdapter,
|
|
263
|
+
resolveAttachmentAdapter,
|
|
264
|
+
parseSSELine,
|
|
265
|
+
AssistantRuntimeProvider,
|
|
266
|
+
useTimbalStream,
|
|
267
|
+
} from "@timbal-ai/timbal-react";
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`parseSSELine` and `AssistantRuntimeProvider` are re-exported so custom runtimes do not need a second `@assistant-ui/react` import for those symbols. `useTimbalStream` exposes the same SSE reducer and `send` / `reload` / `cancel` API without mounting `<Thread>`.
|
|
271
|
+
|
|
141
272
|
### Custom fetch function
|
|
142
273
|
|
|
143
274
|
Pass your own `fetch` to add headers, inject tokens, or proxy requests:
|
|
@@ -168,8 +299,9 @@ Use the `components` prop on `TimbalChat` or `Thread` to replace any part of the
|
|
|
168
299
|
| `UserMessage` | none | built-in user bubble |
|
|
169
300
|
| `AssistantMessage` | none | built-in assistant bubble |
|
|
170
301
|
| `EditComposer` | none | built-in inline edit composer |
|
|
171
|
-
| `Composer` | `placeholder` | built-in composer bar |
|
|
172
|
-
| `Welcome` | `config`, `suggestions` | built-in welcome screen |
|
|
302
|
+
| `Composer` | `placeholder` (+ full `ComposerProps`) | built-in composer bar |
|
|
303
|
+
| `Welcome` | `config`, `suggestions`, `Suggestions` | built-in welcome screen |
|
|
304
|
+
| `Suggestions` | `suggestions` | built-in suggestion chips |
|
|
173
305
|
| `ScrollToBottom` | none | built-in scroll button |
|
|
174
306
|
|
|
175
307
|
Custom slot components read their data via hooks — no props are passed automatically except where noted above.
|
|
@@ -281,6 +413,81 @@ These are re-exported from `@assistant-ui/react` for use inside custom slot comp
|
|
|
281
413
|
|
|
282
414
|
---
|
|
283
415
|
|
|
416
|
+
## Artifacts
|
|
417
|
+
|
|
418
|
+
Agents can return structured JSON **artifacts** — charts, tables, choice widgets, and interactive UI — instead of plain text. The chat UI renders them automatically from tool results or inline ` ```timbal-artifact ` fences.
|
|
419
|
+
|
|
420
|
+
### Tell the agent about the schema
|
|
421
|
+
|
|
422
|
+
Import the ready-made instruction block and append it to your workforce system prompt (or blueprint tool-result docs):
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import { ARTIFACT_AGENT_INSTRUCTIONS } from "@timbal-ai/timbal-react";
|
|
426
|
+
|
|
427
|
+
const systemPrompt = `${basePrompt}\n\n${ARTIFACT_AGENT_INSTRUCTIONS}`;
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
`ARTIFACT_AGENT_INSTRUCTIONS` documents every built-in `type` (`chart`, `table`, `question`, `html`, `json`, `ui`) and the full interactive **`ui` node palette** (hover tooltips, buttons, toggles, sliders, drag).
|
|
431
|
+
|
|
432
|
+
### Subscribe to interactive events
|
|
433
|
+
|
|
434
|
+
`ui` artifacts can fire `{ kind: "emit" }` actions (e.g. after a slider commit or drag). Handle them with `onArtifactEvent` on `Thread` or `TimbalChat`:
|
|
435
|
+
|
|
436
|
+
```tsx
|
|
437
|
+
<TimbalChat
|
|
438
|
+
workforceId="your-workforce-id"
|
|
439
|
+
onArtifactEvent={(event) => {
|
|
440
|
+
console.log(event.name, event.payload);
|
|
441
|
+
// e.g. refetch data, update local UI, call your API
|
|
442
|
+
}}
|
|
443
|
+
/>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
When using `Thread` directly:
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
<TimbalRuntimeProvider workforceId="your-workforce-id">
|
|
450
|
+
<Thread
|
|
451
|
+
onArtifactEvent={(event) => console.log(event.name, event.payload)}
|
|
452
|
+
/>
|
|
453
|
+
</TimbalRuntimeProvider>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Built-in `{ kind: "message" }` actions already append a user message — you only need `onArtifactEvent` for host-side logic beyond that.
|
|
457
|
+
|
|
458
|
+
### Custom artifact renderers
|
|
459
|
+
|
|
460
|
+
Register extra `type` values or override defaults:
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
<TimbalChat
|
|
464
|
+
workforceId="..."
|
|
465
|
+
artifacts={{
|
|
466
|
+
renderers: {
|
|
467
|
+
"my:widget": MyWidgetRenderer,
|
|
468
|
+
},
|
|
469
|
+
}}
|
|
470
|
+
/>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Extend the interactive palette with host-registered `custom` nodes:
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
import {
|
|
477
|
+
UiCustomNodeRegistryProvider,
|
|
478
|
+
TimbalRuntimeProvider,
|
|
479
|
+
Thread,
|
|
480
|
+
} from "@timbal-ai/timbal-react";
|
|
481
|
+
|
|
482
|
+
<UiCustomNodeRegistryProvider renderers={{ "price-card": PriceCard }}>
|
|
483
|
+
<TimbalRuntimeProvider workforceId="...">
|
|
484
|
+
<Thread />
|
|
485
|
+
</TimbalRuntimeProvider>
|
|
486
|
+
</UiCustomNodeRegistryProvider>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
284
491
|
## API reference
|
|
285
492
|
|
|
286
493
|
### `TimbalChat` props
|
|
@@ -292,11 +499,16 @@ These are re-exported from `@assistant-ui/react` for use inside custom slot comp
|
|
|
292
499
|
| `workforceId` | `string` | **required** | ID of the workforce to stream from |
|
|
293
500
|
| `baseUrl` | `string` | `"/api"` | Base URL for API calls. Posts to `{baseUrl}/workforce/{workforceId}/stream` |
|
|
294
501
|
| `fetch` | `(url, options?) => Promise<Response>` | `authFetch` | Custom fetch. Defaults to the built-in auth-aware fetch (Bearer token + auto-refresh) |
|
|
502
|
+
| `attachments` | `boolean \| { uploadUrl?, accept? } \| AttachmentAdapter \| null` | off | `true` or a config object enables the built-in upload adapter; `null` disables; omitted = off |
|
|
503
|
+
| `attachmentsUploadUrl` | `string` | — | Shorthand: enables the default adapter with a custom upload URL |
|
|
504
|
+
| `attachmentsAccept` | `string` | — | Shorthand: MIME `accept` for the default adapter |
|
|
505
|
+
| `debug` | `boolean` | `false` | Log every parsed SSE event to the console with a `[timbal]` prefix |
|
|
295
506
|
| `welcome.heading` | `string` | `"How can I help you today?"` | Welcome screen heading |
|
|
296
507
|
| `welcome.subheading` | `string` | `"Send a message to start a conversation."` | Welcome screen subheading |
|
|
297
508
|
| `suggestions` | `{ title: string; description?: string }[]` | — | Suggestion chips on the welcome screen |
|
|
298
509
|
| `composerPlaceholder` | `string` | `"Send a message..."` | Composer input placeholder |
|
|
299
510
|
| `components` | `ThreadComponents` | — | Override individual UI slots |
|
|
511
|
+
| `onArtifactEvent` | `(event: UiEventEnvelope) => void` | — | Called when a `ui` artifact fires an `emit` action |
|
|
300
512
|
| `maxWidth` | `string` | `"44rem"` | Max width of the message column |
|
|
301
513
|
| `className` | `string` | — | Extra classes on the root element |
|
|
302
514
|
|
|
@@ -311,6 +523,23 @@ Same as `TimbalChat` minus `workforceId`, `baseUrl`, and `fetch` (those live on
|
|
|
311
523
|
| `workforceId` | `string` | **required** | ID of the workforce to stream from |
|
|
312
524
|
| `baseUrl` | `string` | `"/api"` | Base URL for API calls |
|
|
313
525
|
| `fetch` | `(url, options?) => Promise<Response>` | `authFetch` | Custom fetch function |
|
|
526
|
+
| `attachments` | same as `TimbalChat` | off | Enable uploads on the runtime (usually set on `TimbalChat` instead) |
|
|
527
|
+
| `attachmentsUploadUrl` | `string` | — | Shorthand upload URL for the default adapter |
|
|
528
|
+
| `attachmentsAccept` | `string` | — | Shorthand MIME accept for the default adapter |
|
|
529
|
+
| `debug` | `boolean` | `false` | SSE debug logging (see above) |
|
|
530
|
+
|
|
531
|
+
### `TimbalChatShell` props
|
|
532
|
+
|
|
533
|
+
Extends all `TimbalChat` props except `workforceId` is optional.
|
|
534
|
+
|
|
535
|
+
| Prop | Type | Default | Description |
|
|
536
|
+
|---|---|---|---|
|
|
537
|
+
| `workforceId` | `string` | auto from API | When set, skips fetching and hides the built-in selector |
|
|
538
|
+
| `brand` | `ReactNode` | — | Logo or title at the start of the header |
|
|
539
|
+
| `headerActions` | `ReactNode` | — | Trailing header content (logout, theme toggle, etc.) |
|
|
540
|
+
| `hideWorkforceSelector` | `boolean` | `false` | Hide the built-in `<select>` even when multiple agents exist |
|
|
541
|
+
| `className` | `string` | — | Classes on the outer `h-screen` flex container |
|
|
542
|
+
| `headerClassName` | `string` | — | Classes on the header bar |
|
|
314
543
|
|
|
315
544
|
---
|
|
316
545
|
|
|
@@ -351,6 +580,30 @@ export default function App() {
|
|
|
351
580
|
|
|
352
581
|
When `enabled` is `false`, both `SessionProvider` and `AuthGuard` are transparent — no redirects, no API calls.
|
|
353
582
|
|
|
583
|
+
### Embedding in an iframe
|
|
584
|
+
|
|
585
|
+
When the app runs inside an iframe, `SessionProvider` detects embedding and skips the normal cookie refresh flow. Instead:
|
|
586
|
+
|
|
587
|
+
1. The child posts `{ type: "timbal:request-session" }` to `window.parent`.
|
|
588
|
+
2. The parent responds with `{ type: "timbal:auth", token: "<access>", refreshToken?: "<refresh>" }`.
|
|
589
|
+
3. Tokens are stored in localStorage and `fetchCurrentUser()` runs as usual.
|
|
590
|
+
|
|
591
|
+
`useSession()` exposes `isEmbedded: boolean` so you can adjust UI (e.g. hide logout redirects that assume a top-level window).
|
|
592
|
+
|
|
593
|
+
```tsx
|
|
594
|
+
// Parent page
|
|
595
|
+
iframe.contentWindow?.postMessage(
|
|
596
|
+
{ type: "timbal:auth", token: accessToken, refreshToken },
|
|
597
|
+
"*",
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
window.addEventListener("message", (e) => {
|
|
601
|
+
if (e.data?.type === "timbal:request-session") {
|
|
602
|
+
// inject tokens as above
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
```
|
|
606
|
+
|
|
354
607
|
### `useSession` hook
|
|
355
608
|
|
|
356
609
|
Access the current session anywhere inside `SessionProvider`:
|
|
@@ -359,7 +612,7 @@ Access the current session anywhere inside `SessionProvider`:
|
|
|
359
612
|
import { useSession } from "@timbal-ai/timbal-react";
|
|
360
613
|
|
|
361
614
|
function Header() {
|
|
362
|
-
const { user, isAuthenticated, loading, logout } = useSession();
|
|
615
|
+
const { user, isAuthenticated, isEmbedded, loading, logout } = useSession();
|
|
363
616
|
if (loading) return null;
|
|
364
617
|
return (
|
|
365
618
|
<header>
|
|
@@ -405,15 +658,34 @@ if (res.ok) {
|
|
|
405
658
|
|
|
406
659
|
| Export | Description |
|
|
407
660
|
|---|---|
|
|
661
|
+
| `TimbalChatShell` | Header + workforce picker + full-height `TimbalChat` |
|
|
408
662
|
| `Thread` | Full chat UI — messages, composer, attachments, action bar |
|
|
663
|
+
| `Composer` | Standalone composer bar (for custom thread layouts) |
|
|
664
|
+
| `Suggestions` | Suggestion chip grid/row; use with `useResolvedSuggestions` |
|
|
665
|
+
| `WorkforceSelector` | Styled native `<select>` for agent switching |
|
|
409
666
|
| `MarkdownText` | Markdown renderer with GFM, math (KaTeX), and syntax highlighting |
|
|
410
667
|
| `ToolFallback` | Animated "Using tool: …" indicator shown while a tool runs |
|
|
668
|
+
| `ARTIFACT_AGENT_INSTRUCTIONS` | Markdown block to paste into agent system prompts |
|
|
669
|
+
| `ArtifactRegistryProvider` | Scope custom artifact renderers |
|
|
670
|
+
| `UiEventProvider` | Low-level provider for `ui` artifact `emit` actions |
|
|
671
|
+
| `UiCustomNodeRegistryProvider` | Register `{ kind: "custom" }` node renderers |
|
|
672
|
+
| `ArtifactView` | Render a single artifact object |
|
|
673
|
+
| `parseArtifactFromToolResult` | Parse tool output into an artifact |
|
|
411
674
|
| `SyntaxHighlighter` | Shiki-based code highlighter (vitesse-dark / vitesse-light themes) |
|
|
412
675
|
| `UserMessageAttachments` | Attachment thumbnails in user messages |
|
|
413
676
|
| `ComposerAttachments` | Attachment previews inside the composer |
|
|
414
677
|
| `ComposerAddAttachment` | "+" button to add attachments |
|
|
415
678
|
| `TooltipIconButton` | Icon button with a tooltip |
|
|
416
679
|
|
|
680
|
+
### Hooks
|
|
681
|
+
|
|
682
|
+
| Export | Description |
|
|
683
|
+
|---|---|
|
|
684
|
+
| `useWorkforces` | Fetch `{baseUrl}/workforce` and track selection |
|
|
685
|
+
| `useTimbalStream` | Low-level SSE chat state without `<Thread>` |
|
|
686
|
+
| `useTimbalRuntime` | Access runtime context inside custom providers |
|
|
687
|
+
| `useResolvedSuggestions` | Resolve static/async `SuggestionsSource` to an array |
|
|
688
|
+
|
|
417
689
|
### UI primitives
|
|
418
690
|
|
|
419
691
|
Re-exported Radix UI wrappers pre-styled to match the Timbal design system:
|
|
@@ -424,62 +696,72 @@ Re-exported Radix UI wrappers pre-styled to match the Timbal design system:
|
|
|
424
696
|
|
|
425
697
|
## Full example
|
|
426
698
|
|
|
427
|
-
|
|
699
|
+
App shell with optional auth, using `TimbalChatShell` (agent list + chat in one component):
|
|
700
|
+
|
|
701
|
+
```tsx
|
|
702
|
+
// src/App.tsx
|
|
703
|
+
import {
|
|
704
|
+
SessionProvider,
|
|
705
|
+
AuthGuard,
|
|
706
|
+
TooltipProvider,
|
|
707
|
+
TimbalChatShell,
|
|
708
|
+
} from "@timbal-ai/timbal-react";
|
|
709
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
710
|
+
import Home from "./pages/Home";
|
|
711
|
+
|
|
712
|
+
const isAuthEnabled = !!import.meta.env.VITE_TIMBAL_PROJECT_ID;
|
|
713
|
+
|
|
714
|
+
export default function App() {
|
|
715
|
+
return (
|
|
716
|
+
<SessionProvider enabled={isAuthEnabled}>
|
|
717
|
+
<TooltipProvider>
|
|
718
|
+
<BrowserRouter>
|
|
719
|
+
<AuthGuard requireAuth enabled={isAuthEnabled}>
|
|
720
|
+
<Routes>
|
|
721
|
+
<Route path="/" element={<Home />} />
|
|
722
|
+
</Routes>
|
|
723
|
+
</AuthGuard>
|
|
724
|
+
</BrowserRouter>
|
|
725
|
+
</TooltipProvider>
|
|
726
|
+
</SessionProvider>
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
```
|
|
428
730
|
|
|
429
731
|
```tsx
|
|
430
732
|
// src/pages/Home.tsx
|
|
431
|
-
import {
|
|
432
|
-
import type { WorkforceItem } from "@timbal-ai/timbal-sdk";
|
|
433
|
-
import { TimbalChat, Button, authFetch, useSession } from "@timbal-ai/timbal-react";
|
|
733
|
+
import { TimbalChatShell, Button, useSession } from "@timbal-ai/timbal-react";
|
|
434
734
|
import { LogOut } from "lucide-react";
|
|
435
735
|
|
|
436
736
|
const isAuthEnabled = !!import.meta.env.VITE_TIMBAL_PROJECT_ID;
|
|
437
737
|
|
|
438
738
|
export default function Home() {
|
|
439
|
-
const { logout } = useSession();
|
|
440
|
-
const [workforces, setWorkforces] = useState<WorkforceItem[]>([]);
|
|
441
|
-
const [selectedId, setSelectedId] = useState("");
|
|
442
|
-
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
authFetch("/api/workforce")
|
|
445
|
-
.then((r) => r.json())
|
|
446
|
-
.then((data: WorkforceItem[]) => {
|
|
447
|
-
setWorkforces(data);
|
|
448
|
-
const agent = data.find((w) => w.type === "agent") ?? data[0];
|
|
449
|
-
if (agent) setSelectedId(agent.id ?? agent.name ?? "");
|
|
450
|
-
})
|
|
451
|
-
.catch(() => {});
|
|
452
|
-
}, []);
|
|
739
|
+
const { logout, isAuthenticated } = useSession();
|
|
453
740
|
|
|
454
741
|
return (
|
|
455
|
-
<
|
|
456
|
-
<
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
</option>
|
|
462
|
-
))}
|
|
463
|
-
</select>
|
|
464
|
-
|
|
465
|
-
{isAuthEnabled && (
|
|
466
|
-
<Button variant="ghost" size="icon" onClick={logout}>
|
|
467
|
-
<LogOut />
|
|
742
|
+
<TimbalChatShell
|
|
743
|
+
brand={<span className="text-sm font-semibold">My App</span>}
|
|
744
|
+
headerActions={
|
|
745
|
+
isAuthEnabled && isAuthenticated ? (
|
|
746
|
+
<Button variant="ghost" size="icon" onClick={logout} aria-label="Log out">
|
|
747
|
+
<LogOut className="size-4" />
|
|
468
748
|
</Button>
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
749
|
+
) : null
|
|
750
|
+
}
|
|
751
|
+
welcome={{ heading: "How can I help you today?" }}
|
|
752
|
+
suggestions={[
|
|
753
|
+
{ title: "Summarize this week", description: "Recent activity at a glance" },
|
|
754
|
+
{ title: "What can you help with?" },
|
|
755
|
+
]}
|
|
756
|
+
attachments
|
|
757
|
+
debug={import.meta.env.DEV}
|
|
758
|
+
/>
|
|
479
759
|
);
|
|
480
760
|
}
|
|
481
761
|
```
|
|
482
762
|
|
|
763
|
+
For a fully custom header, combine `useWorkforces` with `TimbalChat` instead of `TimbalChatShell` (see [Workforce list hook](#workforce-list-hook)).
|
|
764
|
+
|
|
483
765
|
---
|
|
484
766
|
|
|
485
767
|
## Local development
|