@timbal-ai/timbal-react 0.2.0 → 0.2.2
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 +175 -57
- package/dist/index.cjs +78 -15
- package/dist/index.d.cts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.esm.js +76 -14
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @timbal-ai/timbal-react
|
|
2
2
|
|
|
3
|
-
React components and runtime for building Timbal chat UIs.
|
|
3
|
+
React components and runtime for building Timbal chat UIs. Drop in a single component to get a fully-featured streaming chat interface connected to a Timbal workforce agent.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,15 +10,15 @@ npm install @timbal-ai/timbal-react
|
|
|
10
10
|
bun add @timbal-ai/timbal-react
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**Peer dependencies:**
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install react react-dom @assistant-ui/react @timbal-ai/timbal-sdk
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
###
|
|
19
|
+
### Tailwind setup
|
|
20
20
|
|
|
21
|
-
The package ships pre-built class names
|
|
21
|
+
The package ships pre-built Tailwind class names. Add this `@source` line to your CSS entry file — **without it the components will be unstyled**:
|
|
22
22
|
|
|
23
23
|
```css
|
|
24
24
|
/* src/index.css */
|
|
@@ -29,7 +29,7 @@ The package ships pre-built class names that Tailwind must scan. Add this `@sour
|
|
|
29
29
|
|
|
30
30
|
> Adjust the path if your CSS file lives at a different depth relative to `node_modules`.
|
|
31
31
|
|
|
32
|
-
###
|
|
32
|
+
### CSS imports
|
|
33
33
|
|
|
34
34
|
Import these stylesheets once in your app entry:
|
|
35
35
|
|
|
@@ -41,11 +41,11 @@ import "katex/dist/katex.min.css";
|
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
|
-
##
|
|
44
|
+
## Quick start
|
|
45
45
|
|
|
46
|
-
###
|
|
46
|
+
### Basic usage
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
`TimbalChat` is a single component that handles everything — runtime, streaming, messages, and the composer:
|
|
49
49
|
|
|
50
50
|
```tsx
|
|
51
51
|
import { TimbalChat } from "@timbal-ai/timbal-react";
|
|
@@ -59,7 +59,9 @@ export default function App() {
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
> `TimbalChat` requires a fixed height parent. Use `height: "100vh"` or `flex-1 min-h-0` depending on your layout.
|
|
63
|
+
|
|
64
|
+
### Welcome screen and suggestions
|
|
63
65
|
|
|
64
66
|
```tsx
|
|
65
67
|
<TimbalChat
|
|
@@ -76,7 +78,7 @@ export default function App() {
|
|
|
76
78
|
/>
|
|
77
79
|
```
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
### Placeholder and width
|
|
80
82
|
|
|
81
83
|
```tsx
|
|
82
84
|
<TimbalChat
|
|
@@ -87,9 +89,9 @@ export default function App() {
|
|
|
87
89
|
/>
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
### Switching agents dynamically
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
Pass `key` to fully reset the chat when the workforce changes:
|
|
93
95
|
|
|
94
96
|
```tsx
|
|
95
97
|
const [workforceId, setWorkforceId] = useState("agent-a");
|
|
@@ -104,9 +106,9 @@ const [workforceId, setWorkforceId] = useState("agent-a");
|
|
|
104
106
|
|
|
105
107
|
---
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
## Splitting the runtime and UI
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
`TimbalChat` is a convenience wrapper around `TimbalRuntimeProvider` + `Thread`. Use them separately when you need to place the runtime above the chat — for example, to build a custom header that reads or controls chat state:
|
|
110
112
|
|
|
111
113
|
```tsx
|
|
112
114
|
import { TimbalRuntimeProvider, Thread } from "@timbal-ai/timbal-react";
|
|
@@ -126,7 +128,7 @@ export default function App() {
|
|
|
126
128
|
}
|
|
127
129
|
```
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
### Custom API base URL
|
|
130
132
|
|
|
131
133
|
Useful when your API is mounted at a subpath (e.g. behind a reverse proxy):
|
|
132
134
|
|
|
@@ -136,20 +138,15 @@ Useful when your API is mounted at a subpath (e.g. behind a reverse proxy):
|
|
|
136
138
|
</TimbalRuntimeProvider>
|
|
137
139
|
```
|
|
138
140
|
|
|
139
|
-
|
|
141
|
+
### Custom fetch function
|
|
140
142
|
|
|
141
143
|
Pass your own `fetch` to add headers, inject tokens, or proxy requests:
|
|
142
144
|
|
|
143
145
|
```tsx
|
|
144
|
-
import { TimbalRuntimeProvider, Thread } from "@timbal-ai/timbal-react";
|
|
145
|
-
|
|
146
146
|
const myFetch: typeof fetch = (url, options) => {
|
|
147
147
|
return fetch(url, {
|
|
148
148
|
...options,
|
|
149
|
-
headers: {
|
|
150
|
-
...options?.headers,
|
|
151
|
-
"X-My-Header": "value",
|
|
152
|
-
},
|
|
149
|
+
headers: { ...options?.headers, "X-My-Header": "value" },
|
|
153
150
|
});
|
|
154
151
|
};
|
|
155
152
|
|
|
@@ -160,30 +157,168 @@ const myFetch: typeof fetch = (url, options) => {
|
|
|
160
157
|
|
|
161
158
|
---
|
|
162
159
|
|
|
163
|
-
|
|
160
|
+
## Customizing the UI
|
|
161
|
+
|
|
162
|
+
Use the `components` prop on `TimbalChat` or `Thread` to replace any part of the interface while keeping everything else as the default.
|
|
163
|
+
|
|
164
|
+
### Available slots
|
|
165
|
+
|
|
166
|
+
| Slot | Props forwarded | Default |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `UserMessage` | none | built-in user bubble |
|
|
169
|
+
| `AssistantMessage` | none | built-in assistant bubble |
|
|
170
|
+
| `EditComposer` | none | built-in inline edit composer |
|
|
171
|
+
| `Composer` | `placeholder` | built-in composer bar |
|
|
172
|
+
| `Welcome` | `config`, `suggestions` | built-in welcome screen |
|
|
173
|
+
| `ScrollToBottom` | none | built-in scroll button |
|
|
174
|
+
|
|
175
|
+
Custom slot components read their data via hooks — no props are passed automatically except where noted above.
|
|
176
|
+
|
|
177
|
+
### Custom user message
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { TimbalChat, MessagePrimitive } from "@timbal-ai/timbal-react";
|
|
181
|
+
|
|
182
|
+
const CompactUserMessage = () => (
|
|
183
|
+
<MessagePrimitive.Root className="flex justify-end px-4 py-2">
|
|
184
|
+
<div className="bg-primary text-primary-foreground rounded-2xl px-4 py-2 text-sm max-w-[75%]">
|
|
185
|
+
<MessagePrimitive.Parts />
|
|
186
|
+
</div>
|
|
187
|
+
</MessagePrimitive.Root>
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
<TimbalChat workforceId="..." components={{ UserMessage: CompactUserMessage }} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Custom composer
|
|
194
|
+
|
|
195
|
+
The `Composer` slot receives `placeholder` from the `composerPlaceholder` prop:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import { TimbalChat, ComposerPrimitive } from "@timbal-ai/timbal-react";
|
|
199
|
+
|
|
200
|
+
const MinimalComposer = ({ placeholder }: { placeholder?: string }) => (
|
|
201
|
+
<ComposerPrimitive.Root className="flex items-center gap-2 border rounded-full px-4 py-2">
|
|
202
|
+
<ComposerPrimitive.Input
|
|
203
|
+
placeholder={placeholder ?? "Type here..."}
|
|
204
|
+
className="flex-1 bg-transparent text-sm outline-none"
|
|
205
|
+
rows={1}
|
|
206
|
+
/>
|
|
207
|
+
<ComposerPrimitive.Send className="text-primary font-medium text-sm">
|
|
208
|
+
Send
|
|
209
|
+
</ComposerPrimitive.Send>
|
|
210
|
+
</ComposerPrimitive.Root>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
<TimbalChat workforceId="..." components={{ Composer: MinimalComposer }} />
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Custom welcome screen
|
|
217
|
+
|
|
218
|
+
The `Welcome` slot is always mounted and controls its own visibility. Use `useThread` to replicate the default "show only when the thread is empty" behaviour:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import { TimbalChat, useThread, useThreadRuntime, type ThreadWelcomeProps } from "@timbal-ai/timbal-react";
|
|
222
|
+
|
|
223
|
+
const BrandedWelcome = ({ suggestions }: ThreadWelcomeProps) => {
|
|
224
|
+
const isEmpty = useThread((s) => s.isEmpty);
|
|
225
|
+
const runtime = useThreadRuntime();
|
|
226
|
+
if (!isEmpty) return null;
|
|
227
|
+
return (
|
|
228
|
+
<div className="flex flex-col items-center justify-center h-full gap-4">
|
|
229
|
+
<img src="/logo.svg" className="h-12" />
|
|
230
|
+
<h2 className="text-xl font-semibold">Welcome to Acme AI</h2>
|
|
231
|
+
<div className="flex gap-2 flex-wrap justify-center">
|
|
232
|
+
{suggestions?.map((s) => (
|
|
233
|
+
<button
|
|
234
|
+
key={s.title}
|
|
235
|
+
onClick={() => runtime.append({ role: "user", content: [{ type: "text", text: s.title }] })}
|
|
236
|
+
className="border rounded-full px-4 py-1.5 text-sm hover:bg-muted"
|
|
237
|
+
>
|
|
238
|
+
{s.title}
|
|
239
|
+
</button>
|
|
240
|
+
))}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
<TimbalChat
|
|
247
|
+
workforceId="..."
|
|
248
|
+
suggestions={[{ title: "Get started" }, { title: "Show me an example" }]}
|
|
249
|
+
components={{ Welcome: BrandedWelcome }}
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Mixing slots
|
|
254
|
+
|
|
255
|
+
Override any combination — slots are independent of each other:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
<TimbalChat
|
|
259
|
+
workforceId="..."
|
|
260
|
+
components={{
|
|
261
|
+
UserMessage: CompactUserMessage,
|
|
262
|
+
Composer: MinimalComposer,
|
|
263
|
+
}}
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Hooks and primitives
|
|
268
|
+
|
|
269
|
+
These are re-exported from `@assistant-ui/react` for use inside custom slot components:
|
|
270
|
+
|
|
271
|
+
| Export | Use inside |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `ThreadPrimitive` | Any slot |
|
|
274
|
+
| `MessagePrimitive` | `UserMessage`, `AssistantMessage`, `EditComposer` |
|
|
275
|
+
| `ComposerPrimitive` | `Composer`, `EditComposer` |
|
|
276
|
+
| `ActionBarPrimitive` | `UserMessage`, `AssistantMessage` |
|
|
277
|
+
| `useThread` | Any slot — subscribe to thread state (e.g. `isRunning`, `isEmpty`) |
|
|
278
|
+
| `useThreadRuntime` | Any slot — call actions (e.g. `runtime.append(...)`) |
|
|
279
|
+
| `useMessageRuntime` | `UserMessage`, `AssistantMessage` — edit, reload, branch |
|
|
280
|
+
| `useComposerRuntime` | `Composer`, `EditComposer` — access composer state |
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## API reference
|
|
285
|
+
|
|
286
|
+
### `TimbalChat` props
|
|
287
|
+
|
|
288
|
+
`TimbalChat` accepts all `TimbalRuntimeProvider` props plus all `Thread` props.
|
|
164
289
|
|
|
165
290
|
| Prop | Type | Default | Description |
|
|
166
291
|
|---|---|---|---|
|
|
292
|
+
| `workforceId` | `string` | **required** | ID of the workforce to stream from |
|
|
293
|
+
| `baseUrl` | `string` | `"/api"` | Base URL for API calls. Posts to `{baseUrl}/workforce/{workforceId}/stream` |
|
|
294
|
+
| `fetch` | `(url, options?) => Promise<Response>` | `authFetch` | Custom fetch. Defaults to the built-in auth-aware fetch (Bearer token + auto-refresh) |
|
|
167
295
|
| `welcome.heading` | `string` | `"How can I help you today?"` | Welcome screen heading |
|
|
168
296
|
| `welcome.subheading` | `string` | `"Send a message to start a conversation."` | Welcome screen subheading |
|
|
169
297
|
| `suggestions` | `{ title: string; description?: string }[]` | — | Suggestion chips on the welcome screen |
|
|
170
298
|
| `composerPlaceholder` | `string` | `"Send a message..."` | Composer input placeholder |
|
|
299
|
+
| `components` | `ThreadComponents` | — | Override individual UI slots |
|
|
171
300
|
| `maxWidth` | `string` | `"44rem"` | Max width of the message column |
|
|
172
301
|
| `className` | `string` | — | Extra classes on the root element |
|
|
173
302
|
|
|
303
|
+
### `Thread` props
|
|
304
|
+
|
|
305
|
+
Same as `TimbalChat` minus `workforceId`, `baseUrl`, and `fetch` (those live on `TimbalRuntimeProvider`).
|
|
306
|
+
|
|
174
307
|
### `TimbalRuntimeProvider` props
|
|
175
308
|
|
|
176
309
|
| Prop | Type | Default | Description |
|
|
177
310
|
|---|---|---|---|
|
|
178
|
-
| `workforceId` | `string` |
|
|
179
|
-
| `baseUrl` | `string` | `"/api"` | Base URL for API calls
|
|
180
|
-
| `fetch` | `(url, options?) => Promise<Response>` | `authFetch` | Custom fetch function
|
|
311
|
+
| `workforceId` | `string` | **required** | ID of the workforce to stream from |
|
|
312
|
+
| `baseUrl` | `string` | `"/api"` | Base URL for API calls |
|
|
313
|
+
| `fetch` | `(url, options?) => Promise<Response>` | `authFetch` | Custom fetch function |
|
|
181
314
|
|
|
182
315
|
---
|
|
183
316
|
|
|
184
317
|
## Auth
|
|
185
318
|
|
|
186
|
-
The package includes
|
|
319
|
+
The package includes an optional session/auth system backed by localStorage tokens. The API is expected to expose `/api/auth/login`, `/api/auth/logout`, and `/api/auth/refresh`.
|
|
320
|
+
|
|
321
|
+
Auth is **opt-in** — it only activates when `VITE_TIMBAL_PROJECT_ID` is set in your environment.
|
|
187
322
|
|
|
188
323
|
### Setup
|
|
189
324
|
|
|
@@ -195,7 +330,6 @@ import { SessionProvider, AuthGuard, TooltipProvider } from "@timbal-ai/timbal-r
|
|
|
195
330
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
196
331
|
import Home from "./pages/Home";
|
|
197
332
|
|
|
198
|
-
// Auth is opt-in — only active when VITE_TIMBAL_PROJECT_ID is set
|
|
199
333
|
const isAuthEnabled = !!import.meta.env.VITE_TIMBAL_PROJECT_ID;
|
|
200
334
|
|
|
201
335
|
export default function App() {
|
|
@@ -215,20 +349,7 @@ export default function App() {
|
|
|
215
349
|
}
|
|
216
350
|
```
|
|
217
351
|
|
|
218
|
-
When `enabled` is `false
|
|
219
|
-
|
|
220
|
-
### `SessionProvider` props
|
|
221
|
-
|
|
222
|
-
| Prop | Type | Default | Description |
|
|
223
|
-
|---|---|---|---|
|
|
224
|
-
| `enabled` | `boolean` | `true` | When `false`, session is always `null` and no API calls are made |
|
|
225
|
-
|
|
226
|
-
### `AuthGuard` props
|
|
227
|
-
|
|
228
|
-
| Prop | Type | Default | Description |
|
|
229
|
-
|---|---|---|---|
|
|
230
|
-
| `requireAuth` | `boolean` | `false` | Redirect to login if not authenticated |
|
|
231
|
-
| `enabled` | `boolean` | `true` | When `false`, renders children unconditionally |
|
|
352
|
+
When `enabled` is `false`, both `SessionProvider` and `AuthGuard` are transparent — no redirects, no API calls.
|
|
232
353
|
|
|
233
354
|
### `useSession` hook
|
|
234
355
|
|
|
@@ -239,9 +360,7 @@ import { useSession } from "@timbal-ai/timbal-react";
|
|
|
239
360
|
|
|
240
361
|
function Header() {
|
|
241
362
|
const { user, isAuthenticated, loading, logout } = useSession();
|
|
242
|
-
|
|
243
363
|
if (loading) return null;
|
|
244
|
-
|
|
245
364
|
return (
|
|
246
365
|
<header>
|
|
247
366
|
{isAuthenticated ? (
|
|
@@ -259,25 +378,30 @@ function Header() {
|
|
|
259
378
|
|
|
260
379
|
### `authFetch`
|
|
261
380
|
|
|
262
|
-
A drop-in replacement for `fetch` that attaches the Bearer token from localStorage and auto-refreshes on 401:
|
|
381
|
+
A drop-in replacement for `fetch` that attaches the Bearer token from localStorage and auto-refreshes on 401. It's also the default `fetch` used by `TimbalRuntimeProvider`, so you only need to import it directly for your own API calls (e.g. loading workforce lists):
|
|
263
382
|
|
|
264
383
|
```tsx
|
|
265
384
|
import { authFetch } from "@timbal-ai/timbal-react";
|
|
266
385
|
|
|
267
|
-
// Fetch a list of workforce agents
|
|
268
386
|
const res = await authFetch("/api/workforce");
|
|
269
387
|
if (res.ok) {
|
|
270
388
|
const agents = await res.json();
|
|
271
389
|
}
|
|
272
390
|
```
|
|
273
391
|
|
|
274
|
-
|
|
392
|
+
### Auth prop reference
|
|
393
|
+
|
|
394
|
+
| Component | Prop | Type | Default | Description |
|
|
395
|
+
|---|---|---|---|---|
|
|
396
|
+
| `SessionProvider` | `enabled` | `boolean` | `true` | When `false`, session is always `null` and no API calls are made |
|
|
397
|
+
| `AuthGuard` | `requireAuth` | `boolean` | `false` | Redirect to login if not authenticated |
|
|
398
|
+
| `AuthGuard` | `enabled` | `boolean` | `true` | When `false`, renders children unconditionally |
|
|
275
399
|
|
|
276
400
|
---
|
|
277
401
|
|
|
278
|
-
##
|
|
402
|
+
## Other exports
|
|
279
403
|
|
|
280
|
-
|
|
404
|
+
### Components
|
|
281
405
|
|
|
282
406
|
| Export | Description |
|
|
283
407
|
|---|---|
|
|
@@ -306,12 +430,7 @@ A complete page with agent switching, auth, and a custom header:
|
|
|
306
430
|
// src/pages/Home.tsx
|
|
307
431
|
import { useEffect, useState } from "react";
|
|
308
432
|
import type { WorkforceItem } from "@timbal-ai/timbal-sdk";
|
|
309
|
-
import {
|
|
310
|
-
TimbalChat,
|
|
311
|
-
Button,
|
|
312
|
-
authFetch,
|
|
313
|
-
useSession,
|
|
314
|
-
} from "@timbal-ai/timbal-react";
|
|
433
|
+
import { TimbalChat, Button, authFetch, useSession } from "@timbal-ai/timbal-react";
|
|
315
434
|
import { LogOut } from "lucide-react";
|
|
316
435
|
|
|
317
436
|
const isAuthEnabled = !!import.meta.env.VITE_TIMBAL_PROJECT_ID;
|
|
@@ -368,7 +487,6 @@ export default function Home() {
|
|
|
368
487
|
Install via a local path reference:
|
|
369
488
|
|
|
370
489
|
```json
|
|
371
|
-
// package.json
|
|
372
490
|
{
|
|
373
491
|
"dependencies": {
|
|
374
492
|
"@timbal-ai/timbal-react": "file:../../timbal-react"
|
|
@@ -378,7 +496,7 @@ Install via a local path reference:
|
|
|
378
496
|
|
|
379
497
|
Adjust the relative path to where `timbal-react` lives on your machine.
|
|
380
498
|
|
|
381
|
-
After editing source files, rebuild
|
|
499
|
+
After editing source files, rebuild:
|
|
382
500
|
|
|
383
501
|
```bash
|
|
384
502
|
cd timbal-react
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ActionBarPrimitive: () => import_react13.ActionBarPrimitive,
|
|
33
34
|
AuthGuard: () => AuthGuard,
|
|
34
35
|
Avatar: () => Avatar,
|
|
35
36
|
AvatarFallback: () => AvatarFallback,
|
|
@@ -37,6 +38,7 @@ __export(index_exports, {
|
|
|
37
38
|
Button: () => Button,
|
|
38
39
|
ComposerAddAttachment: () => ComposerAddAttachment,
|
|
39
40
|
ComposerAttachments: () => ComposerAttachments,
|
|
41
|
+
ComposerPrimitive: () => import_react13.ComposerPrimitive,
|
|
40
42
|
Dialog: () => Dialog,
|
|
41
43
|
DialogClose: () => DialogClose,
|
|
42
44
|
DialogContent: () => DialogContent,
|
|
@@ -45,10 +47,12 @@ __export(index_exports, {
|
|
|
45
47
|
DialogTitle: () => DialogTitle,
|
|
46
48
|
DialogTrigger: () => DialogTrigger,
|
|
47
49
|
MarkdownText: () => MarkdownText,
|
|
50
|
+
MessagePrimitive: () => import_react13.MessagePrimitive,
|
|
48
51
|
SessionProvider: () => SessionProvider,
|
|
49
52
|
Shimmer: () => Shimmer,
|
|
50
53
|
SyntaxHighlighter: () => syntax_highlighter_default,
|
|
51
54
|
Thread: () => Thread,
|
|
55
|
+
ThreadPrimitive: () => import_react13.ThreadPrimitive,
|
|
52
56
|
TimbalChat: () => TimbalChat,
|
|
53
57
|
TimbalRuntimeProvider: () => TimbalRuntimeProvider,
|
|
54
58
|
ToolFallback: () => ToolFallback,
|
|
@@ -66,7 +70,13 @@ __export(index_exports, {
|
|
|
66
70
|
getAccessToken: () => getAccessToken,
|
|
67
71
|
getRefreshToken: () => getRefreshToken,
|
|
68
72
|
refreshAccessToken: () => refreshAccessToken,
|
|
69
|
-
|
|
73
|
+
setAccessToken: () => setAccessToken,
|
|
74
|
+
setRefreshToken: () => setRefreshToken,
|
|
75
|
+
useComposerRuntime: () => import_react13.useComposerRuntime,
|
|
76
|
+
useMessageRuntime: () => import_react13.useMessageRuntime,
|
|
77
|
+
useSession: () => useSession,
|
|
78
|
+
useThread: () => import_react13.useThread,
|
|
79
|
+
useThreadRuntime: () => import_react13.useThreadRuntime
|
|
70
80
|
});
|
|
71
81
|
module.exports = __toCommonJS(index_exports);
|
|
72
82
|
|
|
@@ -79,7 +89,9 @@ var import_timbal_sdk = require("@timbal-ai/timbal-sdk");
|
|
|
79
89
|
var ACCESS_TOKEN_KEY = "timbal_project_access_token";
|
|
80
90
|
var REFRESH_TOKEN_KEY = "timbal_project_refresh_token";
|
|
81
91
|
var getAccessToken = () => localStorage.getItem(ACCESS_TOKEN_KEY);
|
|
92
|
+
var setAccessToken = (token) => localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
|
82
93
|
var getRefreshToken = () => localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
94
|
+
var setRefreshToken = (token) => localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
83
95
|
var clearTokens = () => {
|
|
84
96
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
85
97
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
@@ -1273,8 +1285,15 @@ var Thread = ({
|
|
|
1273
1285
|
maxWidth = "44rem",
|
|
1274
1286
|
welcome,
|
|
1275
1287
|
suggestions,
|
|
1276
|
-
composerPlaceholder = "Send a message..."
|
|
1288
|
+
composerPlaceholder = "Send a message...",
|
|
1289
|
+
components
|
|
1277
1290
|
}) => {
|
|
1291
|
+
const WelcomeSlot = components?.Welcome ?? ThreadWelcome;
|
|
1292
|
+
const ComposerSlot = components?.Composer ?? Composer;
|
|
1293
|
+
const UserMessageSlot = components?.UserMessage ?? UserMessage;
|
|
1294
|
+
const AssistantMessageSlot = components?.AssistantMessage ?? AssistantMessage;
|
|
1295
|
+
const EditComposerSlot = components?.EditComposer ?? EditComposer;
|
|
1296
|
+
const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
|
|
1278
1297
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1279
1298
|
import_react11.ThreadPrimitive.Root,
|
|
1280
1299
|
{
|
|
@@ -1289,20 +1308,20 @@ var Thread = ({
|
|
|
1289
1308
|
turnAnchor: "bottom",
|
|
1290
1309
|
className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
|
|
1291
1310
|
children: [
|
|
1292
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1311
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WelcomeSlot, { config: welcome, suggestions }),
|
|
1293
1312
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1294
1313
|
import_react11.ThreadPrimitive.Messages,
|
|
1295
1314
|
{
|
|
1296
1315
|
components: {
|
|
1297
|
-
UserMessage,
|
|
1298
|
-
EditComposer,
|
|
1299
|
-
AssistantMessage
|
|
1316
|
+
UserMessage: UserMessageSlot,
|
|
1317
|
+
EditComposer: EditComposerSlot,
|
|
1318
|
+
AssistantMessage: AssistantMessageSlot
|
|
1300
1319
|
}
|
|
1301
1320
|
}
|
|
1302
1321
|
),
|
|
1303
1322
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react11.ThreadPrimitive.ViewportFooter, { className: "aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6", children: [
|
|
1304
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1305
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1323
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ScrollToBottomSlot, {}),
|
|
1324
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ComposerSlot, { placeholder: composerPlaceholder })
|
|
1306
1325
|
] })
|
|
1307
1326
|
]
|
|
1308
1327
|
}
|
|
@@ -1322,7 +1341,7 @@ var ThreadScrollToBottom = () => {
|
|
|
1322
1341
|
) });
|
|
1323
1342
|
};
|
|
1324
1343
|
var ThreadWelcome = ({ config, suggestions }) => {
|
|
1325
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
|
|
1344
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react11.AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
|
|
1326
1345
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "aui-thread-welcome-message flex size-full flex-col items-center justify-center px-4 text-center", children: [
|
|
1327
1346
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
|
|
1328
1347
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "animate-ai-ring-glow absolute inset-0 rounded-2xl bg-gradient-to-br from-primary/15 to-primary/5 ring-1 ring-primary/15" }),
|
|
@@ -1346,7 +1365,7 @@ var ThreadWelcome = ({ config, suggestions }) => {
|
|
|
1346
1365
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-muted-foreground mt-2 delay-75 duration-200", children: config?.subheading ?? "Send a message to start a conversation." })
|
|
1347
1366
|
] }) }),
|
|
1348
1367
|
suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadSuggestions, { suggestions })
|
|
1349
|
-
] });
|
|
1368
|
+
] }) });
|
|
1350
1369
|
};
|
|
1351
1370
|
var ThreadSuggestions = ({ suggestions }) => {
|
|
1352
1371
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4", children: suggestions.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadSuggestionItem, { title: s.title, description: s.description }, i)) });
|
|
@@ -1534,9 +1553,19 @@ function TimbalChat({
|
|
|
1534
1553
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TimbalRuntimeProvider, { workforceId, baseUrl, fetch: fetch2, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Thread, { ...threadProps }) });
|
|
1535
1554
|
}
|
|
1536
1555
|
|
|
1556
|
+
// src/index.ts
|
|
1557
|
+
var import_react13 = require("@assistant-ui/react");
|
|
1558
|
+
|
|
1537
1559
|
// src/auth/provider.tsx
|
|
1538
1560
|
var import_react12 = require("react");
|
|
1539
1561
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1562
|
+
function isInsideIframe() {
|
|
1563
|
+
try {
|
|
1564
|
+
return typeof window !== "undefined" && window.self !== window.top;
|
|
1565
|
+
} catch {
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1540
1569
|
var SessionContext = (0, import_react12.createContext)(void 0);
|
|
1541
1570
|
var useSession = () => {
|
|
1542
1571
|
const context = (0, import_react12.useContext)(SessionContext);
|
|
@@ -1551,6 +1580,7 @@ var SessionProvider = ({
|
|
|
1551
1580
|
}) => {
|
|
1552
1581
|
const [user, setUser] = (0, import_react12.useState)(null);
|
|
1553
1582
|
const [loading, setLoading] = (0, import_react12.useState)(enabled);
|
|
1583
|
+
const [embedded] = (0, import_react12.useState)(isInsideIframe);
|
|
1554
1584
|
(0, import_react12.useEffect)(() => {
|
|
1555
1585
|
if (!enabled) {
|
|
1556
1586
|
setLoading(false);
|
|
@@ -1583,13 +1613,35 @@ var SessionProvider = ({
|
|
|
1583
1613
|
if (ignore) return;
|
|
1584
1614
|
clearTokens();
|
|
1585
1615
|
}
|
|
1586
|
-
|
|
1616
|
+
if (!ignore && !embedded) {
|
|
1617
|
+
setLoading(false);
|
|
1618
|
+
}
|
|
1587
1619
|
};
|
|
1588
1620
|
restoreSession();
|
|
1621
|
+
let messageCleanup;
|
|
1622
|
+
if (embedded) {
|
|
1623
|
+
const handleMessage = async (event) => {
|
|
1624
|
+
if (ignore) return;
|
|
1625
|
+
if (event.data?.type !== "timbal:auth" || !event.data.token) return;
|
|
1626
|
+
setAccessToken(event.data.token);
|
|
1627
|
+
if (event.data.refreshToken) {
|
|
1628
|
+
setRefreshToken(event.data.refreshToken);
|
|
1629
|
+
}
|
|
1630
|
+
const u = await fetchCurrentUser();
|
|
1631
|
+
if (!ignore) {
|
|
1632
|
+
setUser(u);
|
|
1633
|
+
setLoading(false);
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
window.addEventListener("message", handleMessage);
|
|
1637
|
+
window.parent.postMessage({ type: "timbal:request-session" }, "*");
|
|
1638
|
+
messageCleanup = () => window.removeEventListener("message", handleMessage);
|
|
1639
|
+
}
|
|
1589
1640
|
return () => {
|
|
1590
1641
|
ignore = true;
|
|
1642
|
+
messageCleanup?.();
|
|
1591
1643
|
};
|
|
1592
|
-
}, [enabled]);
|
|
1644
|
+
}, [enabled, embedded]);
|
|
1593
1645
|
const logout = (0, import_react12.useCallback)(() => {
|
|
1594
1646
|
clearTokens();
|
|
1595
1647
|
setUser(null);
|
|
@@ -1607,6 +1659,7 @@ var SessionProvider = ({
|
|
|
1607
1659
|
user,
|
|
1608
1660
|
loading,
|
|
1609
1661
|
isAuthenticated: !!user,
|
|
1662
|
+
isEmbedded: embedded,
|
|
1610
1663
|
logout
|
|
1611
1664
|
},
|
|
1612
1665
|
children
|
|
@@ -1622,14 +1675,14 @@ var AuthGuard = ({
|
|
|
1622
1675
|
requireAuth = false,
|
|
1623
1676
|
enabled = true
|
|
1624
1677
|
}) => {
|
|
1625
|
-
const { isAuthenticated, loading } = useSession();
|
|
1678
|
+
const { isAuthenticated, loading, isEmbedded } = useSession();
|
|
1626
1679
|
if (!enabled) {
|
|
1627
1680
|
return children;
|
|
1628
1681
|
}
|
|
1629
1682
|
if (loading) {
|
|
1630
1683
|
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react6.Loader2, { className: "w-8 h-8 animate-spin" }) });
|
|
1631
1684
|
}
|
|
1632
|
-
if (requireAuth && !isAuthenticated) {
|
|
1685
|
+
if (requireAuth && !isAuthenticated && !isEmbedded) {
|
|
1633
1686
|
const returnTo = encodeURIComponent(
|
|
1634
1687
|
window.location.pathname + window.location.search
|
|
1635
1688
|
);
|
|
@@ -1640,6 +1693,7 @@ var AuthGuard = ({
|
|
|
1640
1693
|
};
|
|
1641
1694
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1642
1695
|
0 && (module.exports = {
|
|
1696
|
+
ActionBarPrimitive,
|
|
1643
1697
|
AuthGuard,
|
|
1644
1698
|
Avatar,
|
|
1645
1699
|
AvatarFallback,
|
|
@@ -1647,6 +1701,7 @@ var AuthGuard = ({
|
|
|
1647
1701
|
Button,
|
|
1648
1702
|
ComposerAddAttachment,
|
|
1649
1703
|
ComposerAttachments,
|
|
1704
|
+
ComposerPrimitive,
|
|
1650
1705
|
Dialog,
|
|
1651
1706
|
DialogClose,
|
|
1652
1707
|
DialogContent,
|
|
@@ -1655,10 +1710,12 @@ var AuthGuard = ({
|
|
|
1655
1710
|
DialogTitle,
|
|
1656
1711
|
DialogTrigger,
|
|
1657
1712
|
MarkdownText,
|
|
1713
|
+
MessagePrimitive,
|
|
1658
1714
|
SessionProvider,
|
|
1659
1715
|
Shimmer,
|
|
1660
1716
|
SyntaxHighlighter,
|
|
1661
1717
|
Thread,
|
|
1718
|
+
ThreadPrimitive,
|
|
1662
1719
|
TimbalChat,
|
|
1663
1720
|
TimbalRuntimeProvider,
|
|
1664
1721
|
ToolFallback,
|
|
@@ -1676,5 +1733,11 @@ var AuthGuard = ({
|
|
|
1676
1733
|
getAccessToken,
|
|
1677
1734
|
getRefreshToken,
|
|
1678
1735
|
refreshAccessToken,
|
|
1679
|
-
|
|
1736
|
+
setAccessToken,
|
|
1737
|
+
setRefreshToken,
|
|
1738
|
+
useComposerRuntime,
|
|
1739
|
+
useMessageRuntime,
|
|
1740
|
+
useSession,
|
|
1741
|
+
useThread,
|
|
1742
|
+
useThreadRuntime
|
|
1680
1743
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { ReactNode, FC, ComponentPropsWithRef, ElementType } from 'react';
|
|
3
|
+
import React__default, { ReactNode, FC, ComponentType, ComponentPropsWithRef, ElementType } from 'react';
|
|
4
4
|
import { ToolCallMessagePartComponent } from '@assistant-ui/react';
|
|
5
|
+
export { ActionBarPrimitive, ComposerPrimitive, MessagePrimitive, ThreadPrimitive, useComposerRuntime, useMessageRuntime, useThread, useThreadRuntime } from '@assistant-ui/react';
|
|
5
6
|
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
6
7
|
import { VariantProps } from 'class-variance-authority';
|
|
7
8
|
import { SyntaxHighlighterProps } from '@assistant-ui/react-markdown';
|
|
@@ -34,6 +35,26 @@ interface ThreadWelcomeConfig {
|
|
|
34
35
|
heading?: string;
|
|
35
36
|
subheading?: string;
|
|
36
37
|
}
|
|
38
|
+
interface ThreadWelcomeProps {
|
|
39
|
+
config?: ThreadWelcomeConfig;
|
|
40
|
+
suggestions?: ThreadSuggestion[];
|
|
41
|
+
}
|
|
42
|
+
interface ThreadComponents {
|
|
43
|
+
/** Replace the user message bubble. Access message content via `MessagePrimitive.Parts`. */
|
|
44
|
+
UserMessage?: ComponentType;
|
|
45
|
+
/** Replace the assistant message bubble. Access message content via `MessagePrimitive.Parts`. */
|
|
46
|
+
AssistantMessage?: ComponentType;
|
|
47
|
+
/** Replace the inline edit composer. */
|
|
48
|
+
EditComposer?: ComponentType;
|
|
49
|
+
/** Replace the composer (input bar). Receives `placeholder` from `composerPlaceholder`. */
|
|
50
|
+
Composer?: ComponentType<{
|
|
51
|
+
placeholder?: string;
|
|
52
|
+
}>;
|
|
53
|
+
/** Replace the welcome / empty state. Receives `config` and `suggestions` props. Controls its own visibility — use `useThread(s => s.isEmpty)` to replicate the default behaviour. */
|
|
54
|
+
Welcome?: ComponentType<ThreadWelcomeProps>;
|
|
55
|
+
/** Replace the scroll-to-bottom button. */
|
|
56
|
+
ScrollToBottom?: ComponentType;
|
|
57
|
+
}
|
|
37
58
|
interface ThreadProps {
|
|
38
59
|
className?: string;
|
|
39
60
|
/** Max width of the message column. Default: "44rem" */
|
|
@@ -44,6 +65,8 @@ interface ThreadProps {
|
|
|
44
65
|
suggestions?: ThreadSuggestion[];
|
|
45
66
|
/** Composer input placeholder. Default: "Send a message..." */
|
|
46
67
|
composerPlaceholder?: string;
|
|
68
|
+
/** Override individual UI slots while keeping the rest as defaults. */
|
|
69
|
+
components?: ThreadComponents;
|
|
47
70
|
}
|
|
48
71
|
declare const Thread: FC<ThreadProps>;
|
|
49
72
|
|
|
@@ -79,6 +102,7 @@ interface SessionContextType {
|
|
|
79
102
|
user: Session | null;
|
|
80
103
|
loading: boolean;
|
|
81
104
|
isAuthenticated: boolean;
|
|
105
|
+
isEmbedded: boolean;
|
|
82
106
|
logout: () => void;
|
|
83
107
|
}
|
|
84
108
|
declare const useSession: () => SessionContextType;
|
|
@@ -98,7 +122,9 @@ interface AuthGuardProps {
|
|
|
98
122
|
declare const AuthGuard: React__default.FC<AuthGuardProps>;
|
|
99
123
|
|
|
100
124
|
declare const getAccessToken: () => string | null;
|
|
125
|
+
declare const setAccessToken: (token: string) => void;
|
|
101
126
|
declare const getRefreshToken: () => string | null;
|
|
127
|
+
declare const setRefreshToken: (token: string) => void;
|
|
102
128
|
declare const clearTokens: () => void;
|
|
103
129
|
declare const refreshAccessToken: () => Promise<boolean>;
|
|
104
130
|
declare const authFetch: (url: string, options?: RequestInit) => Promise<Response>;
|
|
@@ -136,4 +162,4 @@ declare const Shimmer: React.MemoExoticComponent<({ children, as: Component, cla
|
|
|
136
162
|
|
|
137
163
|
declare function cn(...inputs: ClassValue[]): string;
|
|
138
164
|
|
|
139
|
-
export { AuthGuard, Avatar, AvatarFallback, AvatarImage, Button, ComposerAddAttachment, ComposerAttachments, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, MarkdownText, SessionProvider, Shimmer, ShikiSyntaxHighlighter as SyntaxHighlighter, type TextShimmerProps, Thread, type ThreadProps, type ThreadSuggestion, type ThreadWelcomeConfig, TimbalChat, type TimbalChatProps, TimbalRuntimeProvider, type TimbalRuntimeProviderProps, ToolFallback, Tooltip, TooltipContent, TooltipIconButton, type TooltipIconButtonProps, TooltipProvider, TooltipTrigger, UserMessageAttachments, authFetch, buttonVariants, clearTokens, cn, fetchCurrentUser, getAccessToken, getRefreshToken, refreshAccessToken, useSession };
|
|
165
|
+
export { AuthGuard, Avatar, AvatarFallback, AvatarImage, Button, ComposerAddAttachment, ComposerAttachments, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, MarkdownText, SessionProvider, Shimmer, ShikiSyntaxHighlighter as SyntaxHighlighter, type TextShimmerProps, Thread, type ThreadComponents, type ThreadProps, type ThreadSuggestion, type ThreadWelcomeConfig, type ThreadWelcomeProps, TimbalChat, type TimbalChatProps, TimbalRuntimeProvider, type TimbalRuntimeProviderProps, ToolFallback, Tooltip, TooltipContent, TooltipIconButton, type TooltipIconButtonProps, TooltipProvider, TooltipTrigger, UserMessageAttachments, authFetch, buttonVariants, clearTokens, cn, fetchCurrentUser, getAccessToken, getRefreshToken, refreshAccessToken, setAccessToken, setRefreshToken, useSession };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { ReactNode, FC, ComponentPropsWithRef, ElementType } from 'react';
|
|
3
|
+
import React__default, { ReactNode, FC, ComponentType, ComponentPropsWithRef, ElementType } from 'react';
|
|
4
4
|
import { ToolCallMessagePartComponent } from '@assistant-ui/react';
|
|
5
|
+
export { ActionBarPrimitive, ComposerPrimitive, MessagePrimitive, ThreadPrimitive, useComposerRuntime, useMessageRuntime, useThread, useThreadRuntime } from '@assistant-ui/react';
|
|
5
6
|
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
6
7
|
import { VariantProps } from 'class-variance-authority';
|
|
7
8
|
import { SyntaxHighlighterProps } from '@assistant-ui/react-markdown';
|
|
@@ -34,6 +35,26 @@ interface ThreadWelcomeConfig {
|
|
|
34
35
|
heading?: string;
|
|
35
36
|
subheading?: string;
|
|
36
37
|
}
|
|
38
|
+
interface ThreadWelcomeProps {
|
|
39
|
+
config?: ThreadWelcomeConfig;
|
|
40
|
+
suggestions?: ThreadSuggestion[];
|
|
41
|
+
}
|
|
42
|
+
interface ThreadComponents {
|
|
43
|
+
/** Replace the user message bubble. Access message content via `MessagePrimitive.Parts`. */
|
|
44
|
+
UserMessage?: ComponentType;
|
|
45
|
+
/** Replace the assistant message bubble. Access message content via `MessagePrimitive.Parts`. */
|
|
46
|
+
AssistantMessage?: ComponentType;
|
|
47
|
+
/** Replace the inline edit composer. */
|
|
48
|
+
EditComposer?: ComponentType;
|
|
49
|
+
/** Replace the composer (input bar). Receives `placeholder` from `composerPlaceholder`. */
|
|
50
|
+
Composer?: ComponentType<{
|
|
51
|
+
placeholder?: string;
|
|
52
|
+
}>;
|
|
53
|
+
/** Replace the welcome / empty state. Receives `config` and `suggestions` props. Controls its own visibility — use `useThread(s => s.isEmpty)` to replicate the default behaviour. */
|
|
54
|
+
Welcome?: ComponentType<ThreadWelcomeProps>;
|
|
55
|
+
/** Replace the scroll-to-bottom button. */
|
|
56
|
+
ScrollToBottom?: ComponentType;
|
|
57
|
+
}
|
|
37
58
|
interface ThreadProps {
|
|
38
59
|
className?: string;
|
|
39
60
|
/** Max width of the message column. Default: "44rem" */
|
|
@@ -44,6 +65,8 @@ interface ThreadProps {
|
|
|
44
65
|
suggestions?: ThreadSuggestion[];
|
|
45
66
|
/** Composer input placeholder. Default: "Send a message..." */
|
|
46
67
|
composerPlaceholder?: string;
|
|
68
|
+
/** Override individual UI slots while keeping the rest as defaults. */
|
|
69
|
+
components?: ThreadComponents;
|
|
47
70
|
}
|
|
48
71
|
declare const Thread: FC<ThreadProps>;
|
|
49
72
|
|
|
@@ -79,6 +102,7 @@ interface SessionContextType {
|
|
|
79
102
|
user: Session | null;
|
|
80
103
|
loading: boolean;
|
|
81
104
|
isAuthenticated: boolean;
|
|
105
|
+
isEmbedded: boolean;
|
|
82
106
|
logout: () => void;
|
|
83
107
|
}
|
|
84
108
|
declare const useSession: () => SessionContextType;
|
|
@@ -98,7 +122,9 @@ interface AuthGuardProps {
|
|
|
98
122
|
declare const AuthGuard: React__default.FC<AuthGuardProps>;
|
|
99
123
|
|
|
100
124
|
declare const getAccessToken: () => string | null;
|
|
125
|
+
declare const setAccessToken: (token: string) => void;
|
|
101
126
|
declare const getRefreshToken: () => string | null;
|
|
127
|
+
declare const setRefreshToken: (token: string) => void;
|
|
102
128
|
declare const clearTokens: () => void;
|
|
103
129
|
declare const refreshAccessToken: () => Promise<boolean>;
|
|
104
130
|
declare const authFetch: (url: string, options?: RequestInit) => Promise<Response>;
|
|
@@ -136,4 +162,4 @@ declare const Shimmer: React.MemoExoticComponent<({ children, as: Component, cla
|
|
|
136
162
|
|
|
137
163
|
declare function cn(...inputs: ClassValue[]): string;
|
|
138
164
|
|
|
139
|
-
export { AuthGuard, Avatar, AvatarFallback, AvatarImage, Button, ComposerAddAttachment, ComposerAttachments, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, MarkdownText, SessionProvider, Shimmer, ShikiSyntaxHighlighter as SyntaxHighlighter, type TextShimmerProps, Thread, type ThreadProps, type ThreadSuggestion, type ThreadWelcomeConfig, TimbalChat, type TimbalChatProps, TimbalRuntimeProvider, type TimbalRuntimeProviderProps, ToolFallback, Tooltip, TooltipContent, TooltipIconButton, type TooltipIconButtonProps, TooltipProvider, TooltipTrigger, UserMessageAttachments, authFetch, buttonVariants, clearTokens, cn, fetchCurrentUser, getAccessToken, getRefreshToken, refreshAccessToken, useSession };
|
|
165
|
+
export { AuthGuard, Avatar, AvatarFallback, AvatarImage, Button, ComposerAddAttachment, ComposerAttachments, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, MarkdownText, SessionProvider, Shimmer, ShikiSyntaxHighlighter as SyntaxHighlighter, type TextShimmerProps, Thread, type ThreadComponents, type ThreadProps, type ThreadSuggestion, type ThreadWelcomeConfig, type ThreadWelcomeProps, TimbalChat, type TimbalChatProps, TimbalRuntimeProvider, type TimbalRuntimeProviderProps, ToolFallback, Tooltip, TooltipContent, TooltipIconButton, type TooltipIconButtonProps, TooltipProvider, TooltipTrigger, UserMessageAttachments, authFetch, buttonVariants, clearTokens, cn, fetchCurrentUser, getAccessToken, getRefreshToken, refreshAccessToken, setAccessToken, setRefreshToken, useSession };
|
package/dist/index.esm.js
CHANGED
|
@@ -10,7 +10,9 @@ import { parseSSELine } from "@timbal-ai/timbal-sdk";
|
|
|
10
10
|
var ACCESS_TOKEN_KEY = "timbal_project_access_token";
|
|
11
11
|
var REFRESH_TOKEN_KEY = "timbal_project_refresh_token";
|
|
12
12
|
var getAccessToken = () => localStorage.getItem(ACCESS_TOKEN_KEY);
|
|
13
|
+
var setAccessToken = (token) => localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
|
13
14
|
var getRefreshToken = () => localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
15
|
+
var setRefreshToken = (token) => localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
|
14
16
|
var clearTokens = () => {
|
|
15
17
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
16
18
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
@@ -1236,8 +1238,15 @@ var Thread = ({
|
|
|
1236
1238
|
maxWidth = "44rem",
|
|
1237
1239
|
welcome,
|
|
1238
1240
|
suggestions,
|
|
1239
|
-
composerPlaceholder = "Send a message..."
|
|
1241
|
+
composerPlaceholder = "Send a message...",
|
|
1242
|
+
components
|
|
1240
1243
|
}) => {
|
|
1244
|
+
const WelcomeSlot = components?.Welcome ?? ThreadWelcome;
|
|
1245
|
+
const ComposerSlot = components?.Composer ?? Composer;
|
|
1246
|
+
const UserMessageSlot = components?.UserMessage ?? UserMessage;
|
|
1247
|
+
const AssistantMessageSlot = components?.AssistantMessage ?? AssistantMessage;
|
|
1248
|
+
const EditComposerSlot = components?.EditComposer ?? EditComposer;
|
|
1249
|
+
const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
|
|
1241
1250
|
return /* @__PURE__ */ jsx12(
|
|
1242
1251
|
ThreadPrimitive.Root,
|
|
1243
1252
|
{
|
|
@@ -1252,20 +1261,20 @@ var Thread = ({
|
|
|
1252
1261
|
turnAnchor: "bottom",
|
|
1253
1262
|
className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
|
|
1254
1263
|
children: [
|
|
1255
|
-
/* @__PURE__ */ jsx12(
|
|
1264
|
+
/* @__PURE__ */ jsx12(WelcomeSlot, { config: welcome, suggestions }),
|
|
1256
1265
|
/* @__PURE__ */ jsx12(
|
|
1257
1266
|
ThreadPrimitive.Messages,
|
|
1258
1267
|
{
|
|
1259
1268
|
components: {
|
|
1260
|
-
UserMessage,
|
|
1261
|
-
EditComposer,
|
|
1262
|
-
AssistantMessage
|
|
1269
|
+
UserMessage: UserMessageSlot,
|
|
1270
|
+
EditComposer: EditComposerSlot,
|
|
1271
|
+
AssistantMessage: AssistantMessageSlot
|
|
1263
1272
|
}
|
|
1264
1273
|
}
|
|
1265
1274
|
),
|
|
1266
1275
|
/* @__PURE__ */ jsxs7(ThreadPrimitive.ViewportFooter, { className: "aui-thread-viewport-footer sticky bottom-0 mx-auto mt-auto flex w-full max-w-(--thread-max-width) flex-col gap-4 overflow-visible rounded-t-3xl bg-background pb-4 md:pb-6", children: [
|
|
1267
|
-
/* @__PURE__ */ jsx12(
|
|
1268
|
-
/* @__PURE__ */ jsx12(
|
|
1276
|
+
/* @__PURE__ */ jsx12(ScrollToBottomSlot, {}),
|
|
1277
|
+
/* @__PURE__ */ jsx12(ComposerSlot, { placeholder: composerPlaceholder })
|
|
1269
1278
|
] })
|
|
1270
1279
|
]
|
|
1271
1280
|
}
|
|
@@ -1285,7 +1294,7 @@ var ThreadScrollToBottom = () => {
|
|
|
1285
1294
|
) });
|
|
1286
1295
|
};
|
|
1287
1296
|
var ThreadWelcome = ({ config, suggestions }) => {
|
|
1288
|
-
return /* @__PURE__ */ jsxs7("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
|
|
1297
|
+
return /* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsxs7("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
|
|
1289
1298
|
/* @__PURE__ */ jsx12("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs7("div", { className: "aui-thread-welcome-message flex size-full flex-col items-center justify-center px-4 text-center", children: [
|
|
1290
1299
|
/* @__PURE__ */ jsxs7("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
|
|
1291
1300
|
/* @__PURE__ */ jsx12("div", { className: "animate-ai-ring-glow absolute inset-0 rounded-2xl bg-gradient-to-br from-primary/15 to-primary/5 ring-1 ring-primary/15" }),
|
|
@@ -1309,7 +1318,7 @@ var ThreadWelcome = ({ config, suggestions }) => {
|
|
|
1309
1318
|
/* @__PURE__ */ jsx12("p", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both text-muted-foreground mt-2 delay-75 duration-200", children: config?.subheading ?? "Send a message to start a conversation." })
|
|
1310
1319
|
] }) }),
|
|
1311
1320
|
suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx12(ThreadSuggestions, { suggestions })
|
|
1312
|
-
] });
|
|
1321
|
+
] }) });
|
|
1313
1322
|
};
|
|
1314
1323
|
var ThreadSuggestions = ({ suggestions }) => {
|
|
1315
1324
|
return /* @__PURE__ */ jsx12("div", { className: "aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4", children: suggestions.map((s, i) => /* @__PURE__ */ jsx12(ThreadSuggestionItem, { title: s.title, description: s.description }, i)) });
|
|
@@ -1497,6 +1506,18 @@ function TimbalChat({
|
|
|
1497
1506
|
return /* @__PURE__ */ jsx13(TimbalRuntimeProvider, { workforceId, baseUrl, fetch: fetch2, children: /* @__PURE__ */ jsx13(Thread, { ...threadProps }) });
|
|
1498
1507
|
}
|
|
1499
1508
|
|
|
1509
|
+
// src/index.ts
|
|
1510
|
+
import {
|
|
1511
|
+
ThreadPrimitive as ThreadPrimitive2,
|
|
1512
|
+
MessagePrimitive as MessagePrimitive3,
|
|
1513
|
+
ComposerPrimitive as ComposerPrimitive3,
|
|
1514
|
+
ActionBarPrimitive as ActionBarPrimitive2,
|
|
1515
|
+
useThread,
|
|
1516
|
+
useThreadRuntime as useThreadRuntime2,
|
|
1517
|
+
useMessageRuntime,
|
|
1518
|
+
useComposerRuntime
|
|
1519
|
+
} from "@assistant-ui/react";
|
|
1520
|
+
|
|
1500
1521
|
// src/auth/provider.tsx
|
|
1501
1522
|
import {
|
|
1502
1523
|
createContext,
|
|
@@ -1506,6 +1527,13 @@ import {
|
|
|
1506
1527
|
useState as useState5
|
|
1507
1528
|
} from "react";
|
|
1508
1529
|
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1530
|
+
function isInsideIframe() {
|
|
1531
|
+
try {
|
|
1532
|
+
return typeof window !== "undefined" && window.self !== window.top;
|
|
1533
|
+
} catch {
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1509
1537
|
var SessionContext = createContext(void 0);
|
|
1510
1538
|
var useSession = () => {
|
|
1511
1539
|
const context = useContext(SessionContext);
|
|
@@ -1520,6 +1548,7 @@ var SessionProvider = ({
|
|
|
1520
1548
|
}) => {
|
|
1521
1549
|
const [user, setUser] = useState5(null);
|
|
1522
1550
|
const [loading, setLoading] = useState5(enabled);
|
|
1551
|
+
const [embedded] = useState5(isInsideIframe);
|
|
1523
1552
|
useEffect4(() => {
|
|
1524
1553
|
if (!enabled) {
|
|
1525
1554
|
setLoading(false);
|
|
@@ -1552,13 +1581,35 @@ var SessionProvider = ({
|
|
|
1552
1581
|
if (ignore) return;
|
|
1553
1582
|
clearTokens();
|
|
1554
1583
|
}
|
|
1555
|
-
|
|
1584
|
+
if (!ignore && !embedded) {
|
|
1585
|
+
setLoading(false);
|
|
1586
|
+
}
|
|
1556
1587
|
};
|
|
1557
1588
|
restoreSession();
|
|
1589
|
+
let messageCleanup;
|
|
1590
|
+
if (embedded) {
|
|
1591
|
+
const handleMessage = async (event) => {
|
|
1592
|
+
if (ignore) return;
|
|
1593
|
+
if (event.data?.type !== "timbal:auth" || !event.data.token) return;
|
|
1594
|
+
setAccessToken(event.data.token);
|
|
1595
|
+
if (event.data.refreshToken) {
|
|
1596
|
+
setRefreshToken(event.data.refreshToken);
|
|
1597
|
+
}
|
|
1598
|
+
const u = await fetchCurrentUser();
|
|
1599
|
+
if (!ignore) {
|
|
1600
|
+
setUser(u);
|
|
1601
|
+
setLoading(false);
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
window.addEventListener("message", handleMessage);
|
|
1605
|
+
window.parent.postMessage({ type: "timbal:request-session" }, "*");
|
|
1606
|
+
messageCleanup = () => window.removeEventListener("message", handleMessage);
|
|
1607
|
+
}
|
|
1558
1608
|
return () => {
|
|
1559
1609
|
ignore = true;
|
|
1610
|
+
messageCleanup?.();
|
|
1560
1611
|
};
|
|
1561
|
-
}, [enabled]);
|
|
1612
|
+
}, [enabled, embedded]);
|
|
1562
1613
|
const logout = useCallback2(() => {
|
|
1563
1614
|
clearTokens();
|
|
1564
1615
|
setUser(null);
|
|
@@ -1576,6 +1627,7 @@ var SessionProvider = ({
|
|
|
1576
1627
|
user,
|
|
1577
1628
|
loading,
|
|
1578
1629
|
isAuthenticated: !!user,
|
|
1630
|
+
isEmbedded: embedded,
|
|
1579
1631
|
logout
|
|
1580
1632
|
},
|
|
1581
1633
|
children
|
|
@@ -1591,14 +1643,14 @@ var AuthGuard = ({
|
|
|
1591
1643
|
requireAuth = false,
|
|
1592
1644
|
enabled = true
|
|
1593
1645
|
}) => {
|
|
1594
|
-
const { isAuthenticated, loading } = useSession();
|
|
1646
|
+
const { isAuthenticated, loading, isEmbedded } = useSession();
|
|
1595
1647
|
if (!enabled) {
|
|
1596
1648
|
return children;
|
|
1597
1649
|
}
|
|
1598
1650
|
if (loading) {
|
|
1599
1651
|
return /* @__PURE__ */ jsx15("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx15(Loader2, { className: "w-8 h-8 animate-spin" }) });
|
|
1600
1652
|
}
|
|
1601
|
-
if (requireAuth && !isAuthenticated) {
|
|
1653
|
+
if (requireAuth && !isAuthenticated && !isEmbedded) {
|
|
1602
1654
|
const returnTo = encodeURIComponent(
|
|
1603
1655
|
window.location.pathname + window.location.search
|
|
1604
1656
|
);
|
|
@@ -1608,6 +1660,7 @@ var AuthGuard = ({
|
|
|
1608
1660
|
return children;
|
|
1609
1661
|
};
|
|
1610
1662
|
export {
|
|
1663
|
+
ActionBarPrimitive2 as ActionBarPrimitive,
|
|
1611
1664
|
AuthGuard,
|
|
1612
1665
|
Avatar,
|
|
1613
1666
|
AvatarFallback,
|
|
@@ -1615,6 +1668,7 @@ export {
|
|
|
1615
1668
|
Button,
|
|
1616
1669
|
ComposerAddAttachment,
|
|
1617
1670
|
ComposerAttachments,
|
|
1671
|
+
ComposerPrimitive3 as ComposerPrimitive,
|
|
1618
1672
|
Dialog,
|
|
1619
1673
|
DialogClose,
|
|
1620
1674
|
DialogContent,
|
|
@@ -1623,10 +1677,12 @@ export {
|
|
|
1623
1677
|
DialogTitle,
|
|
1624
1678
|
DialogTrigger,
|
|
1625
1679
|
MarkdownText,
|
|
1680
|
+
MessagePrimitive3 as MessagePrimitive,
|
|
1626
1681
|
SessionProvider,
|
|
1627
1682
|
Shimmer,
|
|
1628
1683
|
syntax_highlighter_default as SyntaxHighlighter,
|
|
1629
1684
|
Thread,
|
|
1685
|
+
ThreadPrimitive2 as ThreadPrimitive,
|
|
1630
1686
|
TimbalChat,
|
|
1631
1687
|
TimbalRuntimeProvider,
|
|
1632
1688
|
ToolFallback,
|
|
@@ -1644,5 +1700,11 @@ export {
|
|
|
1644
1700
|
getAccessToken,
|
|
1645
1701
|
getRefreshToken,
|
|
1646
1702
|
refreshAccessToken,
|
|
1647
|
-
|
|
1703
|
+
setAccessToken,
|
|
1704
|
+
setRefreshToken,
|
|
1705
|
+
useComposerRuntime,
|
|
1706
|
+
useMessageRuntime,
|
|
1707
|
+
useSession,
|
|
1708
|
+
useThread,
|
|
1709
|
+
useThreadRuntime2 as useThreadRuntime
|
|
1648
1710
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timbal-ai/timbal-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "React components and runtime for building Timbal chat UIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"build": "tsup",
|
|
23
23
|
"build:watch": "tsup --watch",
|
|
24
24
|
"clean": "rm -rf dist",
|
|
25
|
+
"test": "bun test",
|
|
26
|
+
"test:watch": "bun test --watch",
|
|
25
27
|
"typecheck": "tsc --noEmit",
|
|
26
28
|
"prepublishOnly": "bun run build && bun run typecheck"
|
|
27
29
|
},
|
|
@@ -52,9 +54,13 @@
|
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"@assistant-ui/react": "^0.12.10",
|
|
57
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
58
|
+
"@testing-library/react": "^16.3.2",
|
|
59
|
+
"@testing-library/user-event": "^14.6.1",
|
|
55
60
|
"@timbal-ai/timbal-sdk": "0.4.9",
|
|
56
61
|
"@types/react": "^19.2.4",
|
|
57
62
|
"@types/react-dom": "^19.2.3",
|
|
63
|
+
"happy-dom": "^20.8.9",
|
|
58
64
|
"react": "^19.2.0",
|
|
59
65
|
"react-dom": "^19.2.0",
|
|
60
66
|
"tsup": "^8.5.0",
|