@timbal-ai/timbal-react 0.1.0 → 0.1.1
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 +5 -7
- package/dist/index.cjs +4 -73
- package/dist/index.d.cts +1 -5
- package/dist/index.d.ts +1 -5
- package/dist/index.esm.js +4 -73
- 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
|
|
@@ -68,8 +68,6 @@ export default function App() {
|
|
|
68
68
|
| `workforceId` | `string` | — | ID of the workforce to stream from |
|
|
69
69
|
| `baseUrl` | `string` | `"/api"` | Base URL for API calls. Posts to `{baseUrl}/workforce/{workforceId}/stream` |
|
|
70
70
|
| `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
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
@@ -160,7 +158,7 @@ Re-exported Radix UI wrappers pre-styled to match the Timbal design system:
|
|
|
160
158
|
|
|
161
159
|
## Local development
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
Install via a local path reference:
|
|
164
162
|
|
|
165
163
|
```json
|
|
166
164
|
// package.json
|
package/dist/index.cjs
CHANGED
|
@@ -155,7 +155,6 @@ var fetchCurrentUser = async () => {
|
|
|
155
155
|
|
|
156
156
|
// src/runtime/provider.tsx
|
|
157
157
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
158
|
-
var parseLine = import_timbal_sdk.parseSSELine;
|
|
159
158
|
var convertMessage = (message) => ({
|
|
160
159
|
role: message.role,
|
|
161
160
|
content: message.content,
|
|
@@ -175,65 +174,11 @@ function getTextFromMessage(message) {
|
|
|
175
174
|
const part = message.content.find((c) => c.type === "text");
|
|
176
175
|
return part?.type === "text" ? part.text : null;
|
|
177
176
|
}
|
|
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
177
|
function TimbalRuntimeProvider({
|
|
231
178
|
workforceId,
|
|
232
179
|
children,
|
|
233
180
|
baseUrl = "/api",
|
|
234
|
-
fetch: fetchFn
|
|
235
|
-
devFakeStream = false,
|
|
236
|
-
devFakeStreamDelayMs = 75
|
|
181
|
+
fetch: fetchFn
|
|
237
182
|
}) {
|
|
238
183
|
const [messages, setMessages] = (0, import_react.useState)([]);
|
|
239
184
|
const [isRunning, setIsRunning] = (0, import_react.useState)(false);
|
|
@@ -268,20 +213,6 @@ function TimbalRuntimeProvider({
|
|
|
268
213
|
);
|
|
269
214
|
};
|
|
270
215
|
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
216
|
const res = await fetchFnRef.current(`${baseUrl}/workforce/${workforceId}/stream`, {
|
|
286
217
|
method: "POST",
|
|
287
218
|
headers: { "Content-Type": "application/json" },
|
|
@@ -303,7 +234,7 @@ function TimbalRuntimeProvider({
|
|
|
303
234
|
const lines = buffer.split("\n");
|
|
304
235
|
buffer = lines.pop() ?? "";
|
|
305
236
|
for (const line of lines) {
|
|
306
|
-
const event =
|
|
237
|
+
const event = (0, import_timbal_sdk.parseSSELine)(line);
|
|
307
238
|
if (!event) continue;
|
|
308
239
|
if (!capturedRunId && isTopLevelStart(event)) {
|
|
309
240
|
capturedRunId = event.run_id;
|
|
@@ -373,7 +304,7 @@ function TimbalRuntimeProvider({
|
|
|
373
304
|
}
|
|
374
305
|
}
|
|
375
306
|
if (buffer.trim()) {
|
|
376
|
-
const event =
|
|
307
|
+
const event = (0, import_timbal_sdk.parseSSELine)(buffer);
|
|
377
308
|
if (event?.type === "OUTPUT" && parts.length === 0 && event.output) {
|
|
378
309
|
const text = typeof event.output === "string" ? event.output : JSON.stringify(event.output);
|
|
379
310
|
parts.push({ type: "text", text });
|
|
@@ -390,7 +321,7 @@ function TimbalRuntimeProvider({
|
|
|
390
321
|
abortRef.current = null;
|
|
391
322
|
}
|
|
392
323
|
},
|
|
393
|
-
[workforceId, baseUrl
|
|
324
|
+
[workforceId, baseUrl]
|
|
394
325
|
);
|
|
395
326
|
const onNew = (0, import_react.useCallback)(
|
|
396
327
|
async (message) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -23,12 +23,8 @@ 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
|
declare const Thread: FC;
|
|
34
30
|
|
package/dist/index.d.ts
CHANGED
|
@@ -23,12 +23,8 @@ 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
|
declare const Thread: FC;
|
|
34
30
|
|
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) => {
|