@timbal-ai/timbal-react 0.1.0 → 0.2.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 +222 -17
- package/dist/index.cjs +53 -104
- package/dist/index.d.cts +26 -7
- package/dist/index.d.ts +26 -7
- package/dist/index.esm.js +54 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ bun add @timbal-ai/timbal-react
|
|
|
16
16
|
npm install react react-dom @assistant-ui/react @timbal-ai/timbal-sdk
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
### Required: Tailwind setup
|
|
20
20
|
|
|
21
|
-
The package ships pre-built class names that Tailwind must scan. Add this line to your CSS entry file
|
|
21
|
+
The package ships pre-built class names that Tailwind must scan. 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,9 +29,9 @@ The package ships pre-built class names that Tailwind must scan. Add this line t
|
|
|
29
29
|
|
|
30
30
|
> Adjust the path if your CSS file lives at a different depth relative to `node_modules`.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
### Required: CSS imports
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Import these stylesheets once in your app entry:
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
// src/main.tsx
|
|
@@ -43,9 +43,70 @@ import "katex/dist/katex.min.css";
|
|
|
43
43
|
|
|
44
44
|
## Usage
|
|
45
45
|
|
|
46
|
-
###
|
|
46
|
+
### One-liner
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
The simplest way to embed a chat UI. `TimbalChat` handles the runtime and the thread in one component:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { TimbalChat } from "@timbal-ai/timbal-react";
|
|
52
|
+
|
|
53
|
+
export default function App() {
|
|
54
|
+
return (
|
|
55
|
+
<div style={{ height: "100vh" }}>
|
|
56
|
+
<TimbalChat workforceId="your-workforce-id" />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### With welcome screen and suggestions
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<TimbalChat
|
|
66
|
+
workforceId="your-workforce-id"
|
|
67
|
+
welcome={{
|
|
68
|
+
heading: "Hi, I'm your assistant",
|
|
69
|
+
subheading: "Ask me anything about your data.",
|
|
70
|
+
}}
|
|
71
|
+
suggestions={[
|
|
72
|
+
{ title: "Summarize this week", description: "Get a quick overview of recent activity" },
|
|
73
|
+
{ title: "What can you help with?" },
|
|
74
|
+
{ title: "Show me the latest report" },
|
|
75
|
+
]}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### With a custom placeholder and width
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<TimbalChat
|
|
83
|
+
workforceId="your-workforce-id"
|
|
84
|
+
composerPlaceholder="Type a question..."
|
|
85
|
+
maxWidth="60rem"
|
|
86
|
+
className="my-custom-class"
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Switching agents dynamically
|
|
91
|
+
|
|
92
|
+
Use `key` to reset the chat when the workforce changes:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const [workforceId, setWorkforceId] = useState("agent-a");
|
|
96
|
+
|
|
97
|
+
<select onChange={(e) => setWorkforceId(e.target.value)}>
|
|
98
|
+
<option value="agent-a">Agent A</option>
|
|
99
|
+
<option value="agent-b">Agent B</option>
|
|
100
|
+
</select>
|
|
101
|
+
|
|
102
|
+
<TimbalChat workforceId={workforceId} key={workforceId} />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### Compose manually
|
|
108
|
+
|
|
109
|
+
Use `TimbalRuntimeProvider` + `Thread` separately when you need to place the runtime above the chat UI — for example, to build a custom header that reads or controls chat state:
|
|
49
110
|
|
|
50
111
|
```tsx
|
|
51
112
|
import { TimbalRuntimeProvider, Thread } from "@timbal-ai/timbal-react";
|
|
@@ -53,14 +114,63 @@ import { TimbalRuntimeProvider, Thread } from "@timbal-ai/timbal-react";
|
|
|
53
114
|
export default function App() {
|
|
54
115
|
return (
|
|
55
116
|
<TimbalRuntimeProvider workforceId="your-workforce-id">
|
|
56
|
-
<div style={{ height: "100vh" }}>
|
|
57
|
-
<
|
|
117
|
+
<div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
|
|
118
|
+
<header>My App</header>
|
|
119
|
+
<Thread
|
|
120
|
+
composerPlaceholder="Ask anything..."
|
|
121
|
+
className="flex-1 min-h-0"
|
|
122
|
+
/>
|
|
58
123
|
</div>
|
|
59
124
|
</TimbalRuntimeProvider>
|
|
60
125
|
);
|
|
61
126
|
}
|
|
62
127
|
```
|
|
63
128
|
|
|
129
|
+
#### With a custom API base URL
|
|
130
|
+
|
|
131
|
+
Useful when your API is mounted at a subpath (e.g. behind a reverse proxy):
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<TimbalRuntimeProvider workforceId="your-workforce-id" baseUrl="/api">
|
|
135
|
+
<Thread />
|
|
136
|
+
</TimbalRuntimeProvider>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### With a custom fetch function
|
|
140
|
+
|
|
141
|
+
Pass your own `fetch` to add headers, inject tokens, or proxy requests:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { TimbalRuntimeProvider, Thread } from "@timbal-ai/timbal-react";
|
|
145
|
+
|
|
146
|
+
const myFetch: typeof fetch = (url, options) => {
|
|
147
|
+
return fetch(url, {
|
|
148
|
+
...options,
|
|
149
|
+
headers: {
|
|
150
|
+
...options?.headers,
|
|
151
|
+
"X-My-Header": "value",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
<TimbalRuntimeProvider workforceId="your-workforce-id" fetch={myFetch}>
|
|
157
|
+
<Thread />
|
|
158
|
+
</TimbalRuntimeProvider>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### `TimbalChat` / `Thread` props
|
|
164
|
+
|
|
165
|
+
| Prop | Type | Default | Description |
|
|
166
|
+
|---|---|---|---|
|
|
167
|
+
| `welcome.heading` | `string` | `"How can I help you today?"` | Welcome screen heading |
|
|
168
|
+
| `welcome.subheading` | `string` | `"Send a message to start a conversation."` | Welcome screen subheading |
|
|
169
|
+
| `suggestions` | `{ title: string; description?: string }[]` | — | Suggestion chips on the welcome screen |
|
|
170
|
+
| `composerPlaceholder` | `string` | `"Send a message..."` | Composer input placeholder |
|
|
171
|
+
| `maxWidth` | `string` | `"44rem"` | Max width of the message column |
|
|
172
|
+
| `className` | `string` | — | Extra classes on the root element |
|
|
173
|
+
|
|
64
174
|
### `TimbalRuntimeProvider` props
|
|
65
175
|
|
|
66
176
|
| Prop | Type | Default | Description |
|
|
@@ -68,8 +178,6 @@ export default function App() {
|
|
|
68
178
|
| `workforceId` | `string` | — | ID of the workforce to stream from |
|
|
69
179
|
| `baseUrl` | `string` | `"/api"` | Base URL for API calls. Posts to `{baseUrl}/workforce/{workforceId}/stream` |
|
|
70
180
|
| `fetch` | `(url, options?) => Promise<Response>` | `authFetch` | Custom fetch function. Defaults to the built-in auth-aware fetch (Bearer token + auto-refresh) |
|
|
71
|
-
| `devFakeStream` | `boolean` | `false` | Enable fake streaming for local dev/testing without a backend |
|
|
72
|
-
| `devFakeStreamDelayMs` | `number` | `75` | Token delay in ms for fake streaming |
|
|
73
181
|
|
|
74
182
|
---
|
|
75
183
|
|
|
@@ -82,23 +190,33 @@ The package includes a session/auth system backed by localStorage tokens. The AP
|
|
|
82
190
|
Wrap your app with `SessionProvider` and protect routes with `AuthGuard`:
|
|
83
191
|
|
|
84
192
|
```tsx
|
|
193
|
+
// src/App.tsx
|
|
85
194
|
import { SessionProvider, AuthGuard, TooltipProvider } from "@timbal-ai/timbal-react";
|
|
195
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
196
|
+
import Home from "./pages/Home";
|
|
86
197
|
|
|
198
|
+
// Auth is opt-in — only active when VITE_TIMBAL_PROJECT_ID is set
|
|
87
199
|
const isAuthEnabled = !!import.meta.env.VITE_TIMBAL_PROJECT_ID;
|
|
88
200
|
|
|
89
|
-
function App() {
|
|
201
|
+
export default function App() {
|
|
90
202
|
return (
|
|
91
203
|
<SessionProvider enabled={isAuthEnabled}>
|
|
92
204
|
<TooltipProvider>
|
|
93
|
-
<
|
|
94
|
-
<
|
|
95
|
-
|
|
205
|
+
<BrowserRouter>
|
|
206
|
+
<AuthGuard requireAuth enabled={isAuthEnabled}>
|
|
207
|
+
<Routes>
|
|
208
|
+
<Route path="/" element={<Home />} />
|
|
209
|
+
</Routes>
|
|
210
|
+
</AuthGuard>
|
|
211
|
+
</BrowserRouter>
|
|
96
212
|
</TooltipProvider>
|
|
97
213
|
</SessionProvider>
|
|
98
214
|
);
|
|
99
215
|
}
|
|
100
216
|
```
|
|
101
217
|
|
|
218
|
+
When `enabled` is `false` (no project ID configured), both `SessionProvider` and `AuthGuard` are transparent — no redirects, no API calls.
|
|
219
|
+
|
|
102
220
|
### `SessionProvider` props
|
|
103
221
|
|
|
104
222
|
| Prop | Type | Default | Description |
|
|
@@ -114,25 +232,47 @@ function App() {
|
|
|
114
232
|
|
|
115
233
|
### `useSession` hook
|
|
116
234
|
|
|
235
|
+
Access the current session anywhere inside `SessionProvider`:
|
|
236
|
+
|
|
117
237
|
```tsx
|
|
118
238
|
import { useSession } from "@timbal-ai/timbal-react";
|
|
119
239
|
|
|
120
240
|
function Header() {
|
|
121
241
|
const { user, isAuthenticated, loading, logout } = useSession();
|
|
122
|
-
|
|
242
|
+
|
|
243
|
+
if (loading) return null;
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<header>
|
|
247
|
+
{isAuthenticated ? (
|
|
248
|
+
<>
|
|
249
|
+
<span>{user?.email}</span>
|
|
250
|
+
<button onClick={logout}>Log out</button>
|
|
251
|
+
</>
|
|
252
|
+
) : (
|
|
253
|
+
<a href="/login">Log in</a>
|
|
254
|
+
)}
|
|
255
|
+
</header>
|
|
256
|
+
);
|
|
123
257
|
}
|
|
124
258
|
```
|
|
125
259
|
|
|
126
260
|
### `authFetch`
|
|
127
261
|
|
|
128
|
-
A drop-in replacement for `fetch` that attaches the Bearer token and auto-refreshes on 401:
|
|
262
|
+
A drop-in replacement for `fetch` that attaches the Bearer token from localStorage and auto-refreshes on 401:
|
|
129
263
|
|
|
130
264
|
```tsx
|
|
131
265
|
import { authFetch } from "@timbal-ai/timbal-react";
|
|
132
266
|
|
|
267
|
+
// Fetch a list of workforce agents
|
|
133
268
|
const res = await authFetch("/api/workforce");
|
|
269
|
+
if (res.ok) {
|
|
270
|
+
const agents = await res.json();
|
|
271
|
+
}
|
|
134
272
|
```
|
|
135
273
|
|
|
274
|
+
It's also the default `fetch` used by `TimbalRuntimeProvider` — you only need to import it directly for your own API calls (e.g. loading workforce lists, metadata, etc.).
|
|
275
|
+
|
|
136
276
|
---
|
|
137
277
|
|
|
138
278
|
## Components
|
|
@@ -158,9 +298,74 @@ Re-exported Radix UI wrappers pre-styled to match the Timbal design system:
|
|
|
158
298
|
|
|
159
299
|
---
|
|
160
300
|
|
|
301
|
+
## Full example
|
|
302
|
+
|
|
303
|
+
A complete page with agent switching, auth, and a custom header:
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
// src/pages/Home.tsx
|
|
307
|
+
import { useEffect, useState } from "react";
|
|
308
|
+
import type { WorkforceItem } from "@timbal-ai/timbal-sdk";
|
|
309
|
+
import {
|
|
310
|
+
TimbalChat,
|
|
311
|
+
Button,
|
|
312
|
+
authFetch,
|
|
313
|
+
useSession,
|
|
314
|
+
} from "@timbal-ai/timbal-react";
|
|
315
|
+
import { LogOut } from "lucide-react";
|
|
316
|
+
|
|
317
|
+
const isAuthEnabled = !!import.meta.env.VITE_TIMBAL_PROJECT_ID;
|
|
318
|
+
|
|
319
|
+
export default function Home() {
|
|
320
|
+
const { logout } = useSession();
|
|
321
|
+
const [workforces, setWorkforces] = useState<WorkforceItem[]>([]);
|
|
322
|
+
const [selectedId, setSelectedId] = useState("");
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
authFetch("/api/workforce")
|
|
326
|
+
.then((r) => r.json())
|
|
327
|
+
.then((data: WorkforceItem[]) => {
|
|
328
|
+
setWorkforces(data);
|
|
329
|
+
const agent = data.find((w) => w.type === "agent") ?? data[0];
|
|
330
|
+
if (agent) setSelectedId(agent.id ?? agent.name ?? "");
|
|
331
|
+
})
|
|
332
|
+
.catch(() => {});
|
|
333
|
+
}, []);
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
|
|
337
|
+
<header style={{ display: "flex", justifyContent: "space-between", padding: "0.5rem 1.25rem" }}>
|
|
338
|
+
<select value={selectedId} onChange={(e) => setSelectedId(e.target.value)}>
|
|
339
|
+
{workforces.map((w) => (
|
|
340
|
+
<option key={w.id ?? w.name} value={w.id ?? w.name ?? ""}>
|
|
341
|
+
{w.name}
|
|
342
|
+
</option>
|
|
343
|
+
))}
|
|
344
|
+
</select>
|
|
345
|
+
|
|
346
|
+
{isAuthEnabled && (
|
|
347
|
+
<Button variant="ghost" size="icon" onClick={logout}>
|
|
348
|
+
<LogOut />
|
|
349
|
+
</Button>
|
|
350
|
+
)}
|
|
351
|
+
</header>
|
|
352
|
+
|
|
353
|
+
<TimbalChat
|
|
354
|
+
workforceId={selectedId}
|
|
355
|
+
key={selectedId}
|
|
356
|
+
className="flex-1 min-h-0"
|
|
357
|
+
welcome={{ heading: "How can I help you today?" }}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
161
366
|
## Local development
|
|
162
367
|
|
|
163
|
-
|
|
368
|
+
Install via a local path reference:
|
|
164
369
|
|
|
165
370
|
```json
|
|
166
371
|
// package.json
|
package/dist/index.cjs
CHANGED
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
Shimmer: () => Shimmer,
|
|
50
50
|
SyntaxHighlighter: () => syntax_highlighter_default,
|
|
51
51
|
Thread: () => Thread,
|
|
52
|
+
TimbalChat: () => TimbalChat,
|
|
52
53
|
TimbalRuntimeProvider: () => TimbalRuntimeProvider,
|
|
53
54
|
ToolFallback: () => ToolFallback,
|
|
54
55
|
Tooltip: () => Tooltip,
|
|
@@ -155,7 +156,6 @@ var fetchCurrentUser = async () => {
|
|
|
155
156
|
|
|
156
157
|
// src/runtime/provider.tsx
|
|
157
158
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
158
|
-
var parseLine = import_timbal_sdk.parseSSELine;
|
|
159
159
|
var convertMessage = (message) => ({
|
|
160
160
|
role: message.role,
|
|
161
161
|
content: message.content,
|
|
@@ -175,65 +175,11 @@ function getTextFromMessage(message) {
|
|
|
175
175
|
const part = message.content.find((c) => c.type === "text");
|
|
176
176
|
return part?.type === "text" ? part.text : null;
|
|
177
177
|
}
|
|
178
|
-
function waitWithAbort(ms, signal) {
|
|
179
|
-
if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
const timeoutId = setTimeout(() => {
|
|
182
|
-
signal.removeEventListener("abort", onAbort);
|
|
183
|
-
resolve();
|
|
184
|
-
}, ms);
|
|
185
|
-
const onAbort = () => {
|
|
186
|
-
clearTimeout(timeoutId);
|
|
187
|
-
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
188
|
-
};
|
|
189
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
function buildFakeLongResponse(input) {
|
|
193
|
-
const safeInput = input.trim() || "your request";
|
|
194
|
-
const base = [
|
|
195
|
-
`Fake streaming fallback enabled. You asked: "${safeInput}".`,
|
|
196
|
-
"",
|
|
197
|
-
"This is a deliberately long response used to test rendering, scrolling, cancellation, and streaming UX behavior.",
|
|
198
|
-
"",
|
|
199
|
-
"What this stream is exercising:",
|
|
200
|
-
"- Frequent tiny token updates",
|
|
201
|
-
"- Long markdown paragraphs",
|
|
202
|
-
"- Bullet list rendering",
|
|
203
|
-
"- UI action bar behavior while running",
|
|
204
|
-
"- Stop button and abort flow",
|
|
205
|
-
"",
|
|
206
|
-
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vitae mi at augue pulvinar porta. Praesent ullamcorper felis at nibh tincidunt, id sagittis mauris interdum. Integer nec semper dui. Curabitur sed fermentum libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.",
|
|
207
|
-
"",
|
|
208
|
-
"Aliquam luctus purus non bibendum faucibus. Donec at elit eget massa feugiat ultricies. Quisque condimentum, libero in egestas varius, purus justo aliquam sem, vitae feugiat nunc lorem a justo. Sed non tempor est. In hac habitasse platea dictumst.",
|
|
209
|
-
"",
|
|
210
|
-
"If you can read this arriving progressively, the fallback is working as intended."
|
|
211
|
-
].join("\n");
|
|
212
|
-
return `${base}
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
${base}`;
|
|
217
|
-
}
|
|
218
|
-
async function streamFakeLongResponse(input, delayMs, signal, onDelta) {
|
|
219
|
-
const fullResponse = buildFakeLongResponse(input);
|
|
220
|
-
let cursor = 0;
|
|
221
|
-
while (cursor < fullResponse.length) {
|
|
222
|
-
if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
223
|
-
const chunkSize = Math.min(fullResponse.length - cursor, Math.floor(Math.random() * 12) + 2);
|
|
224
|
-
const delta = fullResponse.slice(cursor, cursor + chunkSize);
|
|
225
|
-
cursor += chunkSize;
|
|
226
|
-
onDelta(delta);
|
|
227
|
-
await waitWithAbort(delayMs, signal);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
178
|
function TimbalRuntimeProvider({
|
|
231
179
|
workforceId,
|
|
232
180
|
children,
|
|
233
181
|
baseUrl = "/api",
|
|
234
|
-
fetch: fetchFn
|
|
235
|
-
devFakeStream = false,
|
|
236
|
-
devFakeStreamDelayMs = 75
|
|
182
|
+
fetch: fetchFn
|
|
237
183
|
}) {
|
|
238
184
|
const [messages, setMessages] = (0, import_react.useState)([]);
|
|
239
185
|
const [isRunning, setIsRunning] = (0, import_react.useState)(false);
|
|
@@ -268,20 +214,6 @@ function TimbalRuntimeProvider({
|
|
|
268
214
|
);
|
|
269
215
|
};
|
|
270
216
|
try {
|
|
271
|
-
if (devFakeStream) {
|
|
272
|
-
const fakeId = `call_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
|
|
273
|
-
parts.push({ type: "tool-call", toolCallId: fakeId, toolName: "get_datetime", argsText: "{}" });
|
|
274
|
-
flush();
|
|
275
|
-
await waitWithAbort(2e3, signal);
|
|
276
|
-
parts[0].result = `Current datetime (from tool): ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
277
|
-
flush();
|
|
278
|
-
await waitWithAbort(300, signal);
|
|
279
|
-
await streamFakeLongResponse(input, devFakeStreamDelayMs, signal, (delta) => {
|
|
280
|
-
lastTextPart().text += delta;
|
|
281
|
-
flush();
|
|
282
|
-
});
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
217
|
const res = await fetchFnRef.current(`${baseUrl}/workforce/${workforceId}/stream`, {
|
|
286
218
|
method: "POST",
|
|
287
219
|
headers: { "Content-Type": "application/json" },
|
|
@@ -303,7 +235,7 @@ function TimbalRuntimeProvider({
|
|
|
303
235
|
const lines = buffer.split("\n");
|
|
304
236
|
buffer = lines.pop() ?? "";
|
|
305
237
|
for (const line of lines) {
|
|
306
|
-
const event =
|
|
238
|
+
const event = (0, import_timbal_sdk.parseSSELine)(line);
|
|
307
239
|
if (!event) continue;
|
|
308
240
|
if (!capturedRunId && isTopLevelStart(event)) {
|
|
309
241
|
capturedRunId = event.run_id;
|
|
@@ -373,7 +305,7 @@ function TimbalRuntimeProvider({
|
|
|
373
305
|
}
|
|
374
306
|
}
|
|
375
307
|
if (buffer.trim()) {
|
|
376
|
-
const event =
|
|
308
|
+
const event = (0, import_timbal_sdk.parseSSELine)(buffer);
|
|
377
309
|
if (event?.type === "OUTPUT" && parts.length === 0 && event.output) {
|
|
378
310
|
const text = typeof event.output === "string" ? event.output : JSON.stringify(event.output);
|
|
379
311
|
parts.push({ type: "text", text });
|
|
@@ -390,7 +322,7 @@ function TimbalRuntimeProvider({
|
|
|
390
322
|
abortRef.current = null;
|
|
391
323
|
}
|
|
392
324
|
},
|
|
393
|
-
[workforceId, baseUrl
|
|
325
|
+
[workforceId, baseUrl]
|
|
394
326
|
);
|
|
395
327
|
const onNew = (0, import_react.useCallback)(
|
|
396
328
|
async (message) => {
|
|
@@ -1336,21 +1268,28 @@ ToolFallback.displayName = "ToolFallback";
|
|
|
1336
1268
|
var import_react11 = require("@assistant-ui/react");
|
|
1337
1269
|
var import_lucide_react5 = require("lucide-react");
|
|
1338
1270
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1339
|
-
var Thread = (
|
|
1271
|
+
var Thread = ({
|
|
1272
|
+
className,
|
|
1273
|
+
maxWidth = "44rem",
|
|
1274
|
+
welcome,
|
|
1275
|
+
suggestions,
|
|
1276
|
+
composerPlaceholder = "Send a message..."
|
|
1277
|
+
}) => {
|
|
1340
1278
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1341
1279
|
import_react11.ThreadPrimitive.Root,
|
|
1342
1280
|
{
|
|
1343
|
-
className:
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1281
|
+
className: cn(
|
|
1282
|
+
"aui-root aui-thread-root @container flex h-full flex-col bg-background",
|
|
1283
|
+
className
|
|
1284
|
+
),
|
|
1285
|
+
style: { ["--thread-max-width"]: maxWidth },
|
|
1347
1286
|
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1348
1287
|
import_react11.ThreadPrimitive.Viewport,
|
|
1349
1288
|
{
|
|
1350
1289
|
turnAnchor: "bottom",
|
|
1351
1290
|
className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
|
|
1352
1291
|
children: [
|
|
1353
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react11.AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadWelcome, {}) }),
|
|
1292
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react11.AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadWelcome, { config: welcome, suggestions }) }),
|
|
1354
1293
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1355
1294
|
import_react11.ThreadPrimitive.Messages,
|
|
1356
1295
|
{
|
|
@@ -1363,7 +1302,7 @@ var Thread = () => {
|
|
|
1363
1302
|
),
|
|
1364
1303
|
/* @__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: [
|
|
1365
1304
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadScrollToBottom, {}),
|
|
1366
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Composer, {})
|
|
1305
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Composer, { placeholder: composerPlaceholder })
|
|
1367
1306
|
] })
|
|
1368
1307
|
]
|
|
1369
1308
|
}
|
|
@@ -1382,7 +1321,7 @@ var ThreadScrollToBottom = () => {
|
|
|
1382
1321
|
}
|
|
1383
1322
|
) });
|
|
1384
1323
|
};
|
|
1385
|
-
var ThreadWelcome = () => {
|
|
1324
|
+
var ThreadWelcome = ({ config, suggestions }) => {
|
|
1386
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: [
|
|
1387
1326
|
/* @__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: [
|
|
1388
1327
|
/* @__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: [
|
|
@@ -1403,42 +1342,40 @@ var ThreadWelcome = () => {
|
|
|
1403
1342
|
}
|
|
1404
1343
|
)
|
|
1405
1344
|
] }),
|
|
1406
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h1", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both font-semibold text-2xl duration-200", children: "How can I help you today?" }),
|
|
1407
|
-
/* @__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: "Send a message to start a conversation." })
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h1", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both font-semibold text-2xl duration-200", children: config?.heading ?? "How can I help you today?" }),
|
|
1346
|
+
/* @__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." })
|
|
1408
1347
|
] }) }),
|
|
1409
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadSuggestions, {})
|
|
1348
|
+
suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ThreadSuggestions, { suggestions })
|
|
1410
1349
|
] });
|
|
1411
1350
|
};
|
|
1412
|
-
var ThreadSuggestions = () => {
|
|
1413
|
-
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: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1414
|
-
import_react11.ThreadPrimitive.Suggestions,
|
|
1415
|
-
{
|
|
1416
|
-
components: {
|
|
1417
|
-
Suggestion: ThreadSuggestionItem
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
) });
|
|
1351
|
+
var ThreadSuggestions = ({ suggestions }) => {
|
|
1352
|
+
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)) });
|
|
1421
1353
|
};
|
|
1422
|
-
var ThreadSuggestionItem = () => {
|
|
1423
|
-
|
|
1354
|
+
var ThreadSuggestionItem = ({ title, description }) => {
|
|
1355
|
+
const runtime = (0, import_react11.useThreadRuntime)();
|
|
1356
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
1424
1357
|
Button,
|
|
1425
1358
|
{
|
|
1426
1359
|
variant: "ghost",
|
|
1427
1360
|
className: "aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted",
|
|
1361
|
+
onClick: () => runtime.append({
|
|
1362
|
+
role: "user",
|
|
1363
|
+
content: [{ type: "text", text: title }]
|
|
1364
|
+
}),
|
|
1428
1365
|
children: [
|
|
1429
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children:
|
|
1430
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children:
|
|
1366
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children: title }),
|
|
1367
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children: description })
|
|
1431
1368
|
]
|
|
1432
1369
|
}
|
|
1433
|
-
) })
|
|
1370
|
+
) });
|
|
1434
1371
|
};
|
|
1435
|
-
var Composer = () => {
|
|
1372
|
+
var Composer = ({ placeholder }) => {
|
|
1436
1373
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react11.ComposerPrimitive.Root, { className: "aui-composer-root relative mt-3 flex w-full flex-col", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react11.ComposerPrimitive.AttachmentDropzone, { className: "aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50", children: [
|
|
1437
1374
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ComposerAttachments, {}),
|
|
1438
1375
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
1439
1376
|
import_react11.ComposerPrimitive.Input,
|
|
1440
1377
|
{
|
|
1441
|
-
placeholder: "Send a message...",
|
|
1378
|
+
placeholder: placeholder ?? "Send a message...",
|
|
1442
1379
|
className: "aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0",
|
|
1443
1380
|
rows: 1,
|
|
1444
1381
|
autoFocus: true,
|
|
@@ -1586,9 +1523,20 @@ var EditComposer = () => {
|
|
|
1586
1523
|
] }) });
|
|
1587
1524
|
};
|
|
1588
1525
|
|
|
1526
|
+
// src/components/chat.tsx
|
|
1527
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1528
|
+
function TimbalChat({
|
|
1529
|
+
workforceId,
|
|
1530
|
+
baseUrl,
|
|
1531
|
+
fetch: fetch2,
|
|
1532
|
+
...threadProps
|
|
1533
|
+
}) {
|
|
1534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TimbalRuntimeProvider, { workforceId, baseUrl, fetch: fetch2, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Thread, { ...threadProps }) });
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1589
1537
|
// src/auth/provider.tsx
|
|
1590
1538
|
var import_react12 = require("react");
|
|
1591
|
-
var
|
|
1539
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1592
1540
|
var SessionContext = (0, import_react12.createContext)(void 0);
|
|
1593
1541
|
var useSession = () => {
|
|
1594
1542
|
const context = (0, import_react12.useContext)(SessionContext);
|
|
@@ -1652,7 +1600,7 @@ var SessionProvider = ({
|
|
|
1652
1600
|
() => window.location.href = `/api/auth/login?return_to=${returnTo}`
|
|
1653
1601
|
);
|
|
1654
1602
|
}, []);
|
|
1655
|
-
return /* @__PURE__ */ (0,
|
|
1603
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1656
1604
|
SessionContext.Provider,
|
|
1657
1605
|
{
|
|
1658
1606
|
value: {
|
|
@@ -1668,7 +1616,7 @@ var SessionProvider = ({
|
|
|
1668
1616
|
|
|
1669
1617
|
// src/auth/guard.tsx
|
|
1670
1618
|
var import_lucide_react6 = require("lucide-react");
|
|
1671
|
-
var
|
|
1619
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1672
1620
|
var AuthGuard = ({
|
|
1673
1621
|
children,
|
|
1674
1622
|
requireAuth = false,
|
|
@@ -1679,7 +1627,7 @@ var AuthGuard = ({
|
|
|
1679
1627
|
return children;
|
|
1680
1628
|
}
|
|
1681
1629
|
if (loading) {
|
|
1682
|
-
return /* @__PURE__ */ (0,
|
|
1630
|
+
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" }) });
|
|
1683
1631
|
}
|
|
1684
1632
|
if (requireAuth && !isAuthenticated) {
|
|
1685
1633
|
const returnTo = encodeURIComponent(
|
|
@@ -1711,6 +1659,7 @@ var AuthGuard = ({
|
|
|
1711
1659
|
Shimmer,
|
|
1712
1660
|
SyntaxHighlighter,
|
|
1713
1661
|
Thread,
|
|
1662
|
+
TimbalChat,
|
|
1714
1663
|
TimbalRuntimeProvider,
|
|
1715
1664
|
ToolFallback,
|
|
1716
1665
|
Tooltip,
|
package/dist/index.d.cts
CHANGED
|
@@ -23,14 +23,33 @@ interface TimbalRuntimeProviderProps {
|
|
|
23
23
|
* attaches Bearer tokens from localStorage and auto-refreshes on 401.
|
|
24
24
|
*/
|
|
25
25
|
fetch?: FetchFn;
|
|
26
|
-
/** Enable fake streaming for development/testing. Default: false */
|
|
27
|
-
devFakeStream?: boolean;
|
|
28
|
-
/** Token delay in ms for fake streaming. Default: 75 */
|
|
29
|
-
devFakeStreamDelayMs?: number;
|
|
30
26
|
}
|
|
31
|
-
declare function TimbalRuntimeProvider({ workforceId, children, baseUrl, fetch: fetchFn,
|
|
27
|
+
declare function TimbalRuntimeProvider({ workforceId, children, baseUrl, fetch: fetchFn, }: TimbalRuntimeProviderProps): react_jsx_runtime.JSX.Element;
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
interface ThreadSuggestion {
|
|
30
|
+
title: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
}
|
|
33
|
+
interface ThreadWelcomeConfig {
|
|
34
|
+
heading?: string;
|
|
35
|
+
subheading?: string;
|
|
36
|
+
}
|
|
37
|
+
interface ThreadProps {
|
|
38
|
+
className?: string;
|
|
39
|
+
/** Max width of the message column. Default: "44rem" */
|
|
40
|
+
maxWidth?: string;
|
|
41
|
+
/** Welcome screen text */
|
|
42
|
+
welcome?: ThreadWelcomeConfig;
|
|
43
|
+
/** Suggestion chips shown on the welcome screen */
|
|
44
|
+
suggestions?: ThreadSuggestion[];
|
|
45
|
+
/** Composer input placeholder. Default: "Send a message..." */
|
|
46
|
+
composerPlaceholder?: string;
|
|
47
|
+
}
|
|
48
|
+
declare const Thread: FC<ThreadProps>;
|
|
49
|
+
|
|
50
|
+
interface TimbalChatProps extends Omit<TimbalRuntimeProviderProps, "children">, ThreadProps {
|
|
51
|
+
}
|
|
52
|
+
declare function TimbalChat({ workforceId, baseUrl, fetch, ...threadProps }: TimbalChatProps): react_jsx_runtime.JSX.Element;
|
|
34
53
|
|
|
35
54
|
declare const MarkdownText: React.MemoExoticComponent<() => react_jsx_runtime.JSX.Element>;
|
|
36
55
|
|
|
@@ -117,4 +136,4 @@ declare const Shimmer: React.MemoExoticComponent<({ children, as: Component, cla
|
|
|
117
136
|
|
|
118
137
|
declare function cn(...inputs: ClassValue[]): string;
|
|
119
138
|
|
|
120
|
-
export { AuthGuard, Avatar, AvatarFallback, AvatarImage, Button, ComposerAddAttachment, ComposerAttachments, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, MarkdownText, SessionProvider, Shimmer, ShikiSyntaxHighlighter as SyntaxHighlighter, type TextShimmerProps, Thread, TimbalRuntimeProvider, type TimbalRuntimeProviderProps, ToolFallback, Tooltip, TooltipContent, TooltipIconButton, type TooltipIconButtonProps, TooltipProvider, TooltipTrigger, UserMessageAttachments, authFetch, buttonVariants, clearTokens, cn, fetchCurrentUser, getAccessToken, getRefreshToken, refreshAccessToken, useSession };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -23,14 +23,33 @@ interface TimbalRuntimeProviderProps {
|
|
|
23
23
|
* attaches Bearer tokens from localStorage and auto-refreshes on 401.
|
|
24
24
|
*/
|
|
25
25
|
fetch?: FetchFn;
|
|
26
|
-
/** Enable fake streaming for development/testing. Default: false */
|
|
27
|
-
devFakeStream?: boolean;
|
|
28
|
-
/** Token delay in ms for fake streaming. Default: 75 */
|
|
29
|
-
devFakeStreamDelayMs?: number;
|
|
30
26
|
}
|
|
31
|
-
declare function TimbalRuntimeProvider({ workforceId, children, baseUrl, fetch: fetchFn,
|
|
27
|
+
declare function TimbalRuntimeProvider({ workforceId, children, baseUrl, fetch: fetchFn, }: TimbalRuntimeProviderProps): react_jsx_runtime.JSX.Element;
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
interface ThreadSuggestion {
|
|
30
|
+
title: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
}
|
|
33
|
+
interface ThreadWelcomeConfig {
|
|
34
|
+
heading?: string;
|
|
35
|
+
subheading?: string;
|
|
36
|
+
}
|
|
37
|
+
interface ThreadProps {
|
|
38
|
+
className?: string;
|
|
39
|
+
/** Max width of the message column. Default: "44rem" */
|
|
40
|
+
maxWidth?: string;
|
|
41
|
+
/** Welcome screen text */
|
|
42
|
+
welcome?: ThreadWelcomeConfig;
|
|
43
|
+
/** Suggestion chips shown on the welcome screen */
|
|
44
|
+
suggestions?: ThreadSuggestion[];
|
|
45
|
+
/** Composer input placeholder. Default: "Send a message..." */
|
|
46
|
+
composerPlaceholder?: string;
|
|
47
|
+
}
|
|
48
|
+
declare const Thread: FC<ThreadProps>;
|
|
49
|
+
|
|
50
|
+
interface TimbalChatProps extends Omit<TimbalRuntimeProviderProps, "children">, ThreadProps {
|
|
51
|
+
}
|
|
52
|
+
declare function TimbalChat({ workforceId, baseUrl, fetch, ...threadProps }: TimbalChatProps): react_jsx_runtime.JSX.Element;
|
|
34
53
|
|
|
35
54
|
declare const MarkdownText: React.MemoExoticComponent<() => react_jsx_runtime.JSX.Element>;
|
|
36
55
|
|
|
@@ -117,4 +136,4 @@ declare const Shimmer: React.MemoExoticComponent<({ children, as: Component, cla
|
|
|
117
136
|
|
|
118
137
|
declare function cn(...inputs: ClassValue[]): string;
|
|
119
138
|
|
|
120
|
-
export { AuthGuard, Avatar, AvatarFallback, AvatarImage, Button, ComposerAddAttachment, ComposerAttachments, Dialog, DialogClose, DialogContent, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, MarkdownText, SessionProvider, Shimmer, ShikiSyntaxHighlighter as SyntaxHighlighter, type TextShimmerProps, Thread, TimbalRuntimeProvider, type TimbalRuntimeProviderProps, ToolFallback, Tooltip, TooltipContent, TooltipIconButton, type TooltipIconButtonProps, TooltipProvider, TooltipTrigger, UserMessageAttachments, authFetch, buttonVariants, clearTokens, cn, fetchCurrentUser, getAccessToken, getRefreshToken, refreshAccessToken, useSession };
|
|
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 };
|
package/dist/index.esm.js
CHANGED
|
@@ -87,7 +87,6 @@ var fetchCurrentUser = async () => {
|
|
|
87
87
|
|
|
88
88
|
// src/runtime/provider.tsx
|
|
89
89
|
import { jsx } from "react/jsx-runtime";
|
|
90
|
-
var parseLine = parseSSELine;
|
|
91
90
|
var convertMessage = (message) => ({
|
|
92
91
|
role: message.role,
|
|
93
92
|
content: message.content,
|
|
@@ -107,65 +106,11 @@ function getTextFromMessage(message) {
|
|
|
107
106
|
const part = message.content.find((c) => c.type === "text");
|
|
108
107
|
return part?.type === "text" ? part.text : null;
|
|
109
108
|
}
|
|
110
|
-
function waitWithAbort(ms, signal) {
|
|
111
|
-
if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
112
|
-
return new Promise((resolve, reject) => {
|
|
113
|
-
const timeoutId = setTimeout(() => {
|
|
114
|
-
signal.removeEventListener("abort", onAbort);
|
|
115
|
-
resolve();
|
|
116
|
-
}, ms);
|
|
117
|
-
const onAbort = () => {
|
|
118
|
-
clearTimeout(timeoutId);
|
|
119
|
-
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
120
|
-
};
|
|
121
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function buildFakeLongResponse(input) {
|
|
125
|
-
const safeInput = input.trim() || "your request";
|
|
126
|
-
const base = [
|
|
127
|
-
`Fake streaming fallback enabled. You asked: "${safeInput}".`,
|
|
128
|
-
"",
|
|
129
|
-
"This is a deliberately long response used to test rendering, scrolling, cancellation, and streaming UX behavior.",
|
|
130
|
-
"",
|
|
131
|
-
"What this stream is exercising:",
|
|
132
|
-
"- Frequent tiny token updates",
|
|
133
|
-
"- Long markdown paragraphs",
|
|
134
|
-
"- Bullet list rendering",
|
|
135
|
-
"- UI action bar behavior while running",
|
|
136
|
-
"- Stop button and abort flow",
|
|
137
|
-
"",
|
|
138
|
-
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse vitae mi at augue pulvinar porta. Praesent ullamcorper felis at nibh tincidunt, id sagittis mauris interdum. Integer nec semper dui. Curabitur sed fermentum libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.",
|
|
139
|
-
"",
|
|
140
|
-
"Aliquam luctus purus non bibendum faucibus. Donec at elit eget massa feugiat ultricies. Quisque condimentum, libero in egestas varius, purus justo aliquam sem, vitae feugiat nunc lorem a justo. Sed non tempor est. In hac habitasse platea dictumst.",
|
|
141
|
-
"",
|
|
142
|
-
"If you can read this arriving progressively, the fallback is working as intended."
|
|
143
|
-
].join("\n");
|
|
144
|
-
return `${base}
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
${base}`;
|
|
149
|
-
}
|
|
150
|
-
async function streamFakeLongResponse(input, delayMs, signal, onDelta) {
|
|
151
|
-
const fullResponse = buildFakeLongResponse(input);
|
|
152
|
-
let cursor = 0;
|
|
153
|
-
while (cursor < fullResponse.length) {
|
|
154
|
-
if (signal.aborted) throw new DOMException("The operation was aborted.", "AbortError");
|
|
155
|
-
const chunkSize = Math.min(fullResponse.length - cursor, Math.floor(Math.random() * 12) + 2);
|
|
156
|
-
const delta = fullResponse.slice(cursor, cursor + chunkSize);
|
|
157
|
-
cursor += chunkSize;
|
|
158
|
-
onDelta(delta);
|
|
159
|
-
await waitWithAbort(delayMs, signal);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
109
|
function TimbalRuntimeProvider({
|
|
163
110
|
workforceId,
|
|
164
111
|
children,
|
|
165
112
|
baseUrl = "/api",
|
|
166
|
-
fetch: fetchFn
|
|
167
|
-
devFakeStream = false,
|
|
168
|
-
devFakeStreamDelayMs = 75
|
|
113
|
+
fetch: fetchFn
|
|
169
114
|
}) {
|
|
170
115
|
const [messages, setMessages] = useState([]);
|
|
171
116
|
const [isRunning, setIsRunning] = useState(false);
|
|
@@ -200,20 +145,6 @@ function TimbalRuntimeProvider({
|
|
|
200
145
|
);
|
|
201
146
|
};
|
|
202
147
|
try {
|
|
203
|
-
if (devFakeStream) {
|
|
204
|
-
const fakeId = `call_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
|
|
205
|
-
parts.push({ type: "tool-call", toolCallId: fakeId, toolName: "get_datetime", argsText: "{}" });
|
|
206
|
-
flush();
|
|
207
|
-
await waitWithAbort(2e3, signal);
|
|
208
|
-
parts[0].result = `Current datetime (from tool): ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
209
|
-
flush();
|
|
210
|
-
await waitWithAbort(300, signal);
|
|
211
|
-
await streamFakeLongResponse(input, devFakeStreamDelayMs, signal, (delta) => {
|
|
212
|
-
lastTextPart().text += delta;
|
|
213
|
-
flush();
|
|
214
|
-
});
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
148
|
const res = await fetchFnRef.current(`${baseUrl}/workforce/${workforceId}/stream`, {
|
|
218
149
|
method: "POST",
|
|
219
150
|
headers: { "Content-Type": "application/json" },
|
|
@@ -235,7 +166,7 @@ function TimbalRuntimeProvider({
|
|
|
235
166
|
const lines = buffer.split("\n");
|
|
236
167
|
buffer = lines.pop() ?? "";
|
|
237
168
|
for (const line of lines) {
|
|
238
|
-
const event =
|
|
169
|
+
const event = parseSSELine(line);
|
|
239
170
|
if (!event) continue;
|
|
240
171
|
if (!capturedRunId && isTopLevelStart(event)) {
|
|
241
172
|
capturedRunId = event.run_id;
|
|
@@ -305,7 +236,7 @@ function TimbalRuntimeProvider({
|
|
|
305
236
|
}
|
|
306
237
|
}
|
|
307
238
|
if (buffer.trim()) {
|
|
308
|
-
const event =
|
|
239
|
+
const event = parseSSELine(buffer);
|
|
309
240
|
if (event?.type === "OUTPUT" && parts.length === 0 && event.output) {
|
|
310
241
|
const text = typeof event.output === "string" ? event.output : JSON.stringify(event.output);
|
|
311
242
|
parts.push({ type: "text", text });
|
|
@@ -322,7 +253,7 @@ function TimbalRuntimeProvider({
|
|
|
322
253
|
abortRef.current = null;
|
|
323
254
|
}
|
|
324
255
|
},
|
|
325
|
-
[workforceId, baseUrl
|
|
256
|
+
[workforceId, baseUrl]
|
|
326
257
|
);
|
|
327
258
|
const onNew = useCallback(
|
|
328
259
|
async (message) => {
|
|
@@ -1285,8 +1216,8 @@ import {
|
|
|
1285
1216
|
ComposerPrimitive as ComposerPrimitive2,
|
|
1286
1217
|
ErrorPrimitive,
|
|
1287
1218
|
MessagePrimitive as MessagePrimitive2,
|
|
1288
|
-
|
|
1289
|
-
|
|
1219
|
+
ThreadPrimitive,
|
|
1220
|
+
useThreadRuntime
|
|
1290
1221
|
} from "@assistant-ui/react";
|
|
1291
1222
|
import {
|
|
1292
1223
|
ArrowDownIcon,
|
|
@@ -1300,21 +1231,28 @@ import {
|
|
|
1300
1231
|
SquareIcon
|
|
1301
1232
|
} from "lucide-react";
|
|
1302
1233
|
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1303
|
-
var Thread = (
|
|
1234
|
+
var Thread = ({
|
|
1235
|
+
className,
|
|
1236
|
+
maxWidth = "44rem",
|
|
1237
|
+
welcome,
|
|
1238
|
+
suggestions,
|
|
1239
|
+
composerPlaceholder = "Send a message..."
|
|
1240
|
+
}) => {
|
|
1304
1241
|
return /* @__PURE__ */ jsx12(
|
|
1305
1242
|
ThreadPrimitive.Root,
|
|
1306
1243
|
{
|
|
1307
|
-
className:
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1244
|
+
className: cn(
|
|
1245
|
+
"aui-root aui-thread-root @container flex h-full flex-col bg-background",
|
|
1246
|
+
className
|
|
1247
|
+
),
|
|
1248
|
+
style: { ["--thread-max-width"]: maxWidth },
|
|
1311
1249
|
children: /* @__PURE__ */ jsxs7(
|
|
1312
1250
|
ThreadPrimitive.Viewport,
|
|
1313
1251
|
{
|
|
1314
1252
|
turnAnchor: "bottom",
|
|
1315
1253
|
className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
|
|
1316
1254
|
children: [
|
|
1317
|
-
/* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsx12(ThreadWelcome, {}) }),
|
|
1255
|
+
/* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsx12(ThreadWelcome, { config: welcome, suggestions }) }),
|
|
1318
1256
|
/* @__PURE__ */ jsx12(
|
|
1319
1257
|
ThreadPrimitive.Messages,
|
|
1320
1258
|
{
|
|
@@ -1327,7 +1265,7 @@ var Thread = () => {
|
|
|
1327
1265
|
),
|
|
1328
1266
|
/* @__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: [
|
|
1329
1267
|
/* @__PURE__ */ jsx12(ThreadScrollToBottom, {}),
|
|
1330
|
-
/* @__PURE__ */ jsx12(Composer, {})
|
|
1268
|
+
/* @__PURE__ */ jsx12(Composer, { placeholder: composerPlaceholder })
|
|
1331
1269
|
] })
|
|
1332
1270
|
]
|
|
1333
1271
|
}
|
|
@@ -1346,7 +1284,7 @@ var ThreadScrollToBottom = () => {
|
|
|
1346
1284
|
}
|
|
1347
1285
|
) });
|
|
1348
1286
|
};
|
|
1349
|
-
var ThreadWelcome = () => {
|
|
1287
|
+
var ThreadWelcome = ({ config, suggestions }) => {
|
|
1350
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: [
|
|
1351
1289
|
/* @__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: [
|
|
1352
1290
|
/* @__PURE__ */ jsxs7("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
|
|
@@ -1367,42 +1305,40 @@ var ThreadWelcome = () => {
|
|
|
1367
1305
|
}
|
|
1368
1306
|
)
|
|
1369
1307
|
] }),
|
|
1370
|
-
/* @__PURE__ */ jsx12("h1", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both font-semibold text-2xl duration-200", children: "How can I help you today?" }),
|
|
1371
|
-
/* @__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: "Send a message to start a conversation." })
|
|
1308
|
+
/* @__PURE__ */ jsx12("h1", { className: "aui-thread-welcome-message-inner fade-in slide-in-from-bottom-1 animate-in fill-mode-both font-semibold text-2xl duration-200", children: config?.heading ?? "How can I help you today?" }),
|
|
1309
|
+
/* @__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." })
|
|
1372
1310
|
] }) }),
|
|
1373
|
-
/* @__PURE__ */ jsx12(ThreadSuggestions, {})
|
|
1311
|
+
suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx12(ThreadSuggestions, { suggestions })
|
|
1374
1312
|
] });
|
|
1375
1313
|
};
|
|
1376
|
-
var ThreadSuggestions = () => {
|
|
1377
|
-
return /* @__PURE__ */ jsx12("div", { className: "aui-thread-welcome-suggestions grid w-full @md:grid-cols-2 gap-2 pb-4", children: /* @__PURE__ */ jsx12(
|
|
1378
|
-
ThreadPrimitive.Suggestions,
|
|
1379
|
-
{
|
|
1380
|
-
components: {
|
|
1381
|
-
Suggestion: ThreadSuggestionItem
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
) });
|
|
1314
|
+
var ThreadSuggestions = ({ suggestions }) => {
|
|
1315
|
+
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)) });
|
|
1385
1316
|
};
|
|
1386
|
-
var ThreadSuggestionItem = () => {
|
|
1387
|
-
|
|
1317
|
+
var ThreadSuggestionItem = ({ title, description }) => {
|
|
1318
|
+
const runtime = useThreadRuntime();
|
|
1319
|
+
return /* @__PURE__ */ jsx12("div", { className: "aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200", children: /* @__PURE__ */ jsxs7(
|
|
1388
1320
|
Button,
|
|
1389
1321
|
{
|
|
1390
1322
|
variant: "ghost",
|
|
1391
1323
|
className: "aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted",
|
|
1324
|
+
onClick: () => runtime.append({
|
|
1325
|
+
role: "user",
|
|
1326
|
+
content: [{ type: "text", text: title }]
|
|
1327
|
+
}),
|
|
1392
1328
|
children: [
|
|
1393
|
-
/* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children:
|
|
1394
|
-
/* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children:
|
|
1329
|
+
/* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children: title }),
|
|
1330
|
+
description && /* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children: description })
|
|
1395
1331
|
]
|
|
1396
1332
|
}
|
|
1397
|
-
) })
|
|
1333
|
+
) });
|
|
1398
1334
|
};
|
|
1399
|
-
var Composer = () => {
|
|
1335
|
+
var Composer = ({ placeholder }) => {
|
|
1400
1336
|
return /* @__PURE__ */ jsx12(ComposerPrimitive2.Root, { className: "aui-composer-root relative mt-3 flex w-full flex-col", children: /* @__PURE__ */ jsxs7(ComposerPrimitive2.AttachmentDropzone, { className: "aui-composer-attachment-dropzone flex w-full flex-col rounded-2xl border border-input bg-background px-1 pt-2 outline-none transition-shadow has-[textarea:focus-visible]:border-ring has-[textarea:focus-visible]:ring-2 has-[textarea:focus-visible]:ring-ring/20 data-[dragging=true]:border-ring data-[dragging=true]:border-dashed data-[dragging=true]:bg-accent/50", children: [
|
|
1401
1337
|
/* @__PURE__ */ jsx12(ComposerAttachments, {}),
|
|
1402
1338
|
/* @__PURE__ */ jsx12(
|
|
1403
1339
|
ComposerPrimitive2.Input,
|
|
1404
1340
|
{
|
|
1405
|
-
placeholder: "Send a message...",
|
|
1341
|
+
placeholder: placeholder ?? "Send a message...",
|
|
1406
1342
|
className: "aui-composer-input mb-1 max-h-32 min-h-14 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0",
|
|
1407
1343
|
rows: 1,
|
|
1408
1344
|
autoFocus: true,
|
|
@@ -1550,6 +1486,17 @@ var EditComposer = () => {
|
|
|
1550
1486
|
] }) });
|
|
1551
1487
|
};
|
|
1552
1488
|
|
|
1489
|
+
// src/components/chat.tsx
|
|
1490
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1491
|
+
function TimbalChat({
|
|
1492
|
+
workforceId,
|
|
1493
|
+
baseUrl,
|
|
1494
|
+
fetch: fetch2,
|
|
1495
|
+
...threadProps
|
|
1496
|
+
}) {
|
|
1497
|
+
return /* @__PURE__ */ jsx13(TimbalRuntimeProvider, { workforceId, baseUrl, fetch: fetch2, children: /* @__PURE__ */ jsx13(Thread, { ...threadProps }) });
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1553
1500
|
// src/auth/provider.tsx
|
|
1554
1501
|
import {
|
|
1555
1502
|
createContext,
|
|
@@ -1558,7 +1505,7 @@ import {
|
|
|
1558
1505
|
useEffect as useEffect4,
|
|
1559
1506
|
useState as useState5
|
|
1560
1507
|
} from "react";
|
|
1561
|
-
import { jsx as
|
|
1508
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1562
1509
|
var SessionContext = createContext(void 0);
|
|
1563
1510
|
var useSession = () => {
|
|
1564
1511
|
const context = useContext(SessionContext);
|
|
@@ -1622,7 +1569,7 @@ var SessionProvider = ({
|
|
|
1622
1569
|
() => window.location.href = `/api/auth/login?return_to=${returnTo}`
|
|
1623
1570
|
);
|
|
1624
1571
|
}, []);
|
|
1625
|
-
return /* @__PURE__ */
|
|
1572
|
+
return /* @__PURE__ */ jsx14(
|
|
1626
1573
|
SessionContext.Provider,
|
|
1627
1574
|
{
|
|
1628
1575
|
value: {
|
|
@@ -1638,7 +1585,7 @@ var SessionProvider = ({
|
|
|
1638
1585
|
|
|
1639
1586
|
// src/auth/guard.tsx
|
|
1640
1587
|
import { Loader2 } from "lucide-react";
|
|
1641
|
-
import { jsx as
|
|
1588
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1642
1589
|
var AuthGuard = ({
|
|
1643
1590
|
children,
|
|
1644
1591
|
requireAuth = false,
|
|
@@ -1649,7 +1596,7 @@ var AuthGuard = ({
|
|
|
1649
1596
|
return children;
|
|
1650
1597
|
}
|
|
1651
1598
|
if (loading) {
|
|
1652
|
-
return /* @__PURE__ */
|
|
1599
|
+
return /* @__PURE__ */ jsx15("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx15(Loader2, { className: "w-8 h-8 animate-spin" }) });
|
|
1653
1600
|
}
|
|
1654
1601
|
if (requireAuth && !isAuthenticated) {
|
|
1655
1602
|
const returnTo = encodeURIComponent(
|
|
@@ -1680,6 +1627,7 @@ export {
|
|
|
1680
1627
|
Shimmer,
|
|
1681
1628
|
syntax_highlighter_default as SyntaxHighlighter,
|
|
1682
1629
|
Thread,
|
|
1630
|
+
TimbalChat,
|
|
1683
1631
|
TimbalRuntimeProvider,
|
|
1684
1632
|
ToolFallback,
|
|
1685
1633
|
Tooltip,
|