@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 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
- ## Tailwind setup
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
- ## CSS imports
32
+ ### Required: CSS imports
33
33
 
34
- Some components require stylesheets from their dependencies. Import these once in your app entry:
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
- The package isn't published yet. Install it via a local path reference:
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 = parseLine(line);
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 = parseLine(buffer);
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, devFakeStream, devFakeStreamDelayMs]
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, devFakeStream, devFakeStreamDelayMs, }: TimbalRuntimeProviderProps): react_jsx_runtime.JSX.Element;
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, devFakeStream, devFakeStreamDelayMs, }: TimbalRuntimeProviderProps): react_jsx_runtime.JSX.Element;
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 = parseLine(line);
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 = parseLine(buffer);
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, devFakeStream, devFakeStreamDelayMs]
256
+ [workforceId, baseUrl]
326
257
  );
327
258
  const onNew = useCallback(
328
259
  async (message) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timbal-ai/timbal-react",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "React components and runtime for building Timbal chat UIs",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",