@timbal-ai/timbal-react 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +326 -44
- package/dist/index.cjs +2619 -417
- package/dist/index.d.cts +820 -17
- package/dist/index.d.ts +820 -17
- package/dist/index.esm.js +2646 -471
- package/dist/styles.css +210 -0
- package/package.json +3 -2
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// src/runtime/provider.tsx
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
3
11
|
import {
|
|
4
12
|
useExternalStoreRuntime,
|
|
5
13
|
AssistantRuntimeProvider
|
|
@@ -87,13 +95,506 @@ var fetchCurrentUser = async () => {
|
|
|
87
95
|
}
|
|
88
96
|
};
|
|
89
97
|
|
|
98
|
+
// src/artifacts/types.ts
|
|
99
|
+
function isArtifact(value) {
|
|
100
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/runtime/reducer.ts
|
|
104
|
+
function createReducerState() {
|
|
105
|
+
return { parts: [], toolIndexById: /* @__PURE__ */ new Map() };
|
|
106
|
+
}
|
|
107
|
+
function reduceSseEvent(state, event) {
|
|
108
|
+
switch (event.type) {
|
|
109
|
+
case "DELTA":
|
|
110
|
+
return reduceDelta(state, event.item);
|
|
111
|
+
case "OUTPUT": {
|
|
112
|
+
const path = event.path;
|
|
113
|
+
const isNested = typeof path === "string" && path.includes(".");
|
|
114
|
+
if (!isNested) {
|
|
115
|
+
const errorMessage = readErrorMessage(event);
|
|
116
|
+
if (errorMessage) {
|
|
117
|
+
state.parts.push({ type: "text", text: `**Error:** ${errorMessage}` });
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (isNested) {
|
|
122
|
+
return reduceNestedOutput(
|
|
123
|
+
state,
|
|
124
|
+
path,
|
|
125
|
+
event.output
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return reduceOutput(
|
|
129
|
+
state,
|
|
130
|
+
event.output
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function reduceDelta(state, item) {
|
|
138
|
+
if (!item) return false;
|
|
139
|
+
if (item.type === "text_delta" && typeof item.text_delta === "string") {
|
|
140
|
+
lastTextPart(state).text += item.text_delta;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (item.type === "thinking_delta" && typeof item.thinking_delta === "string") {
|
|
144
|
+
lastThinkingPart(state).text += item.thinking_delta;
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
if (item.type === "tool_use") {
|
|
148
|
+
const toolCallId = item.id || `tool-${crypto.randomUUID()}`;
|
|
149
|
+
const inputStr = stringifyInput(item.input);
|
|
150
|
+
const part = {
|
|
151
|
+
type: "tool-call",
|
|
152
|
+
toolCallId,
|
|
153
|
+
toolName: item.name || "unknown",
|
|
154
|
+
argsText: inputStr,
|
|
155
|
+
status: "running"
|
|
156
|
+
};
|
|
157
|
+
state.parts.push(part);
|
|
158
|
+
state.toolIndexById.set(toolCallId, state.parts.length - 1);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (item.type === "tool_use_delta") {
|
|
162
|
+
const idx = state.toolIndexById.get(item.id);
|
|
163
|
+
if (idx !== void 0 && typeof item.input_delta === "string") {
|
|
164
|
+
state.parts[idx].argsText += item.input_delta;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
function reduceNestedOutput(state, path, output) {
|
|
171
|
+
if (!output || typeof output !== "object") return false;
|
|
172
|
+
if (!Array.isArray(output.content) && isArtifact(output)) {
|
|
173
|
+
const toolName = toolNameFromPath(path);
|
|
174
|
+
if (toolName && attachToolResult(state, { toolName, result: output })) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return reduceOutput(state, output, { toolResultsOnly: true, allowOrphan: false });
|
|
179
|
+
}
|
|
180
|
+
function reduceOutput(state, output, options) {
|
|
181
|
+
if (!output) return false;
|
|
182
|
+
if (typeof output === "string") {
|
|
183
|
+
if (state.parts.length === 0) {
|
|
184
|
+
state.parts.push({ type: "text", text: output });
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (Array.isArray(output.content)) {
|
|
190
|
+
let changed = false;
|
|
191
|
+
const blocks = output.content;
|
|
192
|
+
for (const block of blocks) {
|
|
193
|
+
if (block.type === "tool_use") {
|
|
194
|
+
if (!options?.toolResultsOnly && recordToolUse(state, block)) {
|
|
195
|
+
changed = true;
|
|
196
|
+
}
|
|
197
|
+
} else if (block.type === "tool_result") {
|
|
198
|
+
if (recordToolResult(state, block, options)) changed = true;
|
|
199
|
+
} else if (!options?.toolResultsOnly) {
|
|
200
|
+
if (block.type === "text" && typeof block.text === "string" && !lastTextPart(state).text) {
|
|
201
|
+
lastTextPart(state).text = block.text;
|
|
202
|
+
changed = true;
|
|
203
|
+
} else if (block.type === "thinking" && typeof block.thinking === "string" && !lastThinkingPart(state).text) {
|
|
204
|
+
lastThinkingPart(state).text = block.thinking;
|
|
205
|
+
changed = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return changed;
|
|
210
|
+
}
|
|
211
|
+
if (state.parts.length === 0) {
|
|
212
|
+
state.parts.push({ type: "text", text: JSON.stringify(output) });
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
function recordToolUse(state, block) {
|
|
218
|
+
const id = block.id || `tool-${crypto.randomUUID()}`;
|
|
219
|
+
if (state.toolIndexById.has(id)) return false;
|
|
220
|
+
const inputStr = stringifyInput(block.input);
|
|
221
|
+
const part = {
|
|
222
|
+
type: "tool-call",
|
|
223
|
+
toolCallId: id,
|
|
224
|
+
toolName: block.name || "unknown",
|
|
225
|
+
argsText: inputStr,
|
|
226
|
+
status: "running"
|
|
227
|
+
};
|
|
228
|
+
state.parts.push(part);
|
|
229
|
+
state.toolIndexById.set(id, state.parts.length - 1);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
function recordToolResult(state, block, options) {
|
|
233
|
+
const allowOrphan = options?.allowOrphan !== false;
|
|
234
|
+
const id = block.id || block.tool_use_id || "";
|
|
235
|
+
const { result, resultText } = parseToolResultContent(block.content);
|
|
236
|
+
const toolName = block.name || void 0;
|
|
237
|
+
if (id) {
|
|
238
|
+
const idx = state.toolIndexById.get(id);
|
|
239
|
+
if (idx !== void 0) {
|
|
240
|
+
const part2 = state.parts[idx];
|
|
241
|
+
part2.result = result;
|
|
242
|
+
if (resultText) part2.resultText = resultText;
|
|
243
|
+
part2.status = "complete";
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (!allowOrphan) return false;
|
|
247
|
+
}
|
|
248
|
+
if (!id && toolName && attachToolResult(state, {
|
|
249
|
+
toolName,
|
|
250
|
+
result,
|
|
251
|
+
resultText
|
|
252
|
+
})) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (!id || !allowOrphan) return false;
|
|
256
|
+
const part = {
|
|
257
|
+
type: "tool-call",
|
|
258
|
+
toolCallId: id,
|
|
259
|
+
toolName: toolName || "unknown",
|
|
260
|
+
argsText: "",
|
|
261
|
+
result,
|
|
262
|
+
resultText,
|
|
263
|
+
status: "complete"
|
|
264
|
+
};
|
|
265
|
+
state.parts.push(part);
|
|
266
|
+
state.toolIndexById.set(id, state.parts.length - 1);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
function toolNameFromPath(path) {
|
|
270
|
+
const segment = path.split(".").pop();
|
|
271
|
+
return segment && segment !== "agent" && segment !== "llm" ? segment : null;
|
|
272
|
+
}
|
|
273
|
+
function attachToolResult(state, {
|
|
274
|
+
toolCallId,
|
|
275
|
+
toolName,
|
|
276
|
+
result,
|
|
277
|
+
resultText
|
|
278
|
+
}) {
|
|
279
|
+
if (toolCallId) {
|
|
280
|
+
const idx = state.toolIndexById.get(toolCallId);
|
|
281
|
+
if (idx !== void 0) {
|
|
282
|
+
const part = state.parts[idx];
|
|
283
|
+
part.result = result;
|
|
284
|
+
if (resultText) part.resultText = resultText;
|
|
285
|
+
part.status = "complete";
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (toolName) {
|
|
290
|
+
for (let i = state.parts.length - 1; i >= 0; i--) {
|
|
291
|
+
const part = state.parts[i];
|
|
292
|
+
if (part.type === "tool-call" && part.toolName === toolName && part.result === void 0) {
|
|
293
|
+
part.result = result;
|
|
294
|
+
if (resultText) part.resultText = resultText;
|
|
295
|
+
part.status = "complete";
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
function parseToolResultContent(content) {
|
|
303
|
+
if (typeof content === "string") {
|
|
304
|
+
return { result: content, resultText: content };
|
|
305
|
+
}
|
|
306
|
+
if (!Array.isArray(content)) {
|
|
307
|
+
return { result: content };
|
|
308
|
+
}
|
|
309
|
+
const textChunks = [];
|
|
310
|
+
for (const item of content) {
|
|
311
|
+
if (typeof item === "string") {
|
|
312
|
+
textChunks.push(item);
|
|
313
|
+
} else if (item && typeof item === "object") {
|
|
314
|
+
const obj = item;
|
|
315
|
+
if (obj.type === "text" && typeof obj.text === "string") {
|
|
316
|
+
textChunks.push(obj.text);
|
|
317
|
+
} else if (obj.type === "thinking" && typeof obj.thinking === "string") {
|
|
318
|
+
textChunks.push(obj.thinking);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
result: content,
|
|
324
|
+
resultText: textChunks.length > 0 ? textChunks.join("\n") : void 0
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function readErrorMessage(event) {
|
|
328
|
+
const status = event.status;
|
|
329
|
+
const isErrorStatus = status?.code === "error";
|
|
330
|
+
const error = event.error;
|
|
331
|
+
let type = null;
|
|
332
|
+
let message = null;
|
|
333
|
+
if (isErrorStatus && typeof status?.message === "string" && status.message.length > 0) {
|
|
334
|
+
message = status.message;
|
|
335
|
+
}
|
|
336
|
+
if (!message && typeof error === "string" && error.length > 0) {
|
|
337
|
+
message = error;
|
|
338
|
+
} else if (error && typeof error === "object") {
|
|
339
|
+
const obj = error;
|
|
340
|
+
if (typeof obj.type === "string" && obj.type.length > 0) {
|
|
341
|
+
type = obj.type;
|
|
342
|
+
}
|
|
343
|
+
if (!message && typeof obj.message === "string" && obj.message.length > 0) {
|
|
344
|
+
message = obj.message;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (!message && !isErrorStatus) return null;
|
|
348
|
+
if (!message) return "The agent failed to generate a response.";
|
|
349
|
+
const compact = compactError(message);
|
|
350
|
+
return type ? `${type}: ${compact}` : compact;
|
|
351
|
+
}
|
|
352
|
+
var ERROR_MAX_CHARS = 480;
|
|
353
|
+
function compactError(message) {
|
|
354
|
+
const trimmed = message.split(/\n\s*Traceback \(most recent call last\):/u)[0].trim();
|
|
355
|
+
if (trimmed.length <= ERROR_MAX_CHARS) return trimmed;
|
|
356
|
+
return `${trimmed.slice(0, ERROR_MAX_CHARS).trimEnd()}\u2026`;
|
|
357
|
+
}
|
|
358
|
+
function stringifyInput(input) {
|
|
359
|
+
if (input === void 0 || input === null) return "{}";
|
|
360
|
+
return typeof input === "string" ? input : JSON.stringify(input);
|
|
361
|
+
}
|
|
362
|
+
function lastTextPart(state) {
|
|
363
|
+
const last = state.parts[state.parts.length - 1];
|
|
364
|
+
if (last?.type === "text") return last;
|
|
365
|
+
const next = { type: "text", text: "" };
|
|
366
|
+
state.parts.push(next);
|
|
367
|
+
return next;
|
|
368
|
+
}
|
|
369
|
+
function lastThinkingPart(state) {
|
|
370
|
+
const last = state.parts[state.parts.length - 1];
|
|
371
|
+
if (last?.type === "thinking") return last;
|
|
372
|
+
const next = { type: "thinking", text: "" };
|
|
373
|
+
state.parts.push(next);
|
|
374
|
+
return next;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/runtime/attachments.ts
|
|
378
|
+
async function extractAttachment(attachment) {
|
|
379
|
+
const file = attachment.file;
|
|
380
|
+
let src = null;
|
|
381
|
+
let contentType;
|
|
382
|
+
let name = attachment.name ?? file?.name;
|
|
383
|
+
const content = attachment.content;
|
|
384
|
+
if (content) {
|
|
385
|
+
for (const block of content) {
|
|
386
|
+
if (block.type === "image" && typeof block.image === "string") {
|
|
387
|
+
src = block.image;
|
|
388
|
+
if (typeof block.mimeType === "string") {
|
|
389
|
+
contentType = block.mimeType;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
if (block.type === "file" && typeof block.data === "string") {
|
|
394
|
+
src = block.data;
|
|
395
|
+
if (typeof block.mimeType === "string") contentType = block.mimeType;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (src === null && file) {
|
|
401
|
+
src = await fileToDataUrl(file);
|
|
402
|
+
if (!contentType) contentType = file.type || void 0;
|
|
403
|
+
if (!name) name = file.name;
|
|
404
|
+
}
|
|
405
|
+
if (!src) return null;
|
|
406
|
+
if (!contentType) contentType = mimeFromDataUrl(src);
|
|
407
|
+
const rawType = String(attachment.type ?? "file");
|
|
408
|
+
const type = rawType === "image" || rawType === "document" ? rawType : "file";
|
|
409
|
+
return {
|
|
410
|
+
id: attachment.id ?? crypto.randomUUID(),
|
|
411
|
+
type,
|
|
412
|
+
...name !== void 0 ? { name } : {},
|
|
413
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
414
|
+
dataUrl: src
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function fileToDataUrl(file) {
|
|
418
|
+
return new Promise((resolve, reject) => {
|
|
419
|
+
const reader = new FileReader();
|
|
420
|
+
reader.onload = () => resolve(reader.result);
|
|
421
|
+
reader.onerror = () => reject(reader.error);
|
|
422
|
+
reader.readAsDataURL(file);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function mimeFromDataUrl(dataUrl) {
|
|
426
|
+
const match = /^data:([^;,]+)[;,]/.exec(dataUrl);
|
|
427
|
+
return match?.[1];
|
|
428
|
+
}
|
|
429
|
+
function buildPromptBody({
|
|
430
|
+
input,
|
|
431
|
+
attachments,
|
|
432
|
+
parentId
|
|
433
|
+
}) {
|
|
434
|
+
const context = { parent_id: parentId };
|
|
435
|
+
const files = attachments ?? [];
|
|
436
|
+
if (files.length === 0) {
|
|
437
|
+
return { prompt: input, context };
|
|
438
|
+
}
|
|
439
|
+
const parts = [];
|
|
440
|
+
if (input) parts.push({ type: "text", text: input });
|
|
441
|
+
for (const attachment of files) {
|
|
442
|
+
parts.push({ type: "file", file: attachment.dataUrl });
|
|
443
|
+
}
|
|
444
|
+
return { prompt: parts, context };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/runtime/upload-adapter.ts
|
|
448
|
+
var DEFAULT_UPLOAD_ACCEPT = "image/*,application/pdf,text/*,.md,.json,.csv,.tsv,.xlsx,.docx";
|
|
449
|
+
function createDefaultAttachmentAdapter({
|
|
450
|
+
baseUrl = "",
|
|
451
|
+
uploadUrl,
|
|
452
|
+
fetch: fetchFn = authFetch,
|
|
453
|
+
accept = DEFAULT_UPLOAD_ACCEPT
|
|
454
|
+
} = {}) {
|
|
455
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
456
|
+
const resolvedUploadUrl = uploadUrl ?? `${base}/files/upload`;
|
|
457
|
+
return {
|
|
458
|
+
accept,
|
|
459
|
+
async add({ file }) {
|
|
460
|
+
const isImage = file.type.startsWith("image/");
|
|
461
|
+
const pending = {
|
|
462
|
+
id: crypto.randomUUID(),
|
|
463
|
+
type: isImage ? "image" : "file",
|
|
464
|
+
name: file.name,
|
|
465
|
+
contentType: file.type || "application/octet-stream",
|
|
466
|
+
file,
|
|
467
|
+
status: { type: "requires-action", reason: "composer-send" }
|
|
468
|
+
};
|
|
469
|
+
return pending;
|
|
470
|
+
},
|
|
471
|
+
async send(attachment) {
|
|
472
|
+
const fd = new FormData();
|
|
473
|
+
fd.append("file", attachment.file);
|
|
474
|
+
const res = await fetchFn(resolvedUploadUrl, { method: "POST", body: fd });
|
|
475
|
+
if (!res.ok) {
|
|
476
|
+
const detail = await res.text().catch(() => "");
|
|
477
|
+
throw new Error(
|
|
478
|
+
`Attachment upload failed (${res.status}): ${detail || res.statusText}`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
const remoteUrl = await readUploadedUrl(res);
|
|
482
|
+
const mime = attachment.contentType ?? "application/octet-stream";
|
|
483
|
+
const filename = attachment.name;
|
|
484
|
+
const complete = {
|
|
485
|
+
...attachment,
|
|
486
|
+
status: { type: "complete" },
|
|
487
|
+
content: mime.startsWith("image/") ? [{ type: "image", image: remoteUrl, filename }] : [{ type: "file", data: remoteUrl, mimeType: mime, filename }]
|
|
488
|
+
};
|
|
489
|
+
return complete;
|
|
490
|
+
},
|
|
491
|
+
async remove() {
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
var createUploadAttachmentAdapter = createDefaultAttachmentAdapter;
|
|
496
|
+
async function readUploadedUrl(res) {
|
|
497
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
498
|
+
if (contentType.includes("application/json")) {
|
|
499
|
+
const data = await res.json();
|
|
500
|
+
const candidate = data.url ?? data.signed_url ?? data.id;
|
|
501
|
+
if (typeof candidate === "string" && candidate.length > 0) {
|
|
502
|
+
return candidate;
|
|
503
|
+
}
|
|
504
|
+
throw new Error(
|
|
505
|
+
"Attachment upload response did not include a `url`, `signed_url`, or `id` field."
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
const text = (await res.text()).trim();
|
|
509
|
+
if (!text) {
|
|
510
|
+
throw new Error("Attachment upload response was empty.");
|
|
511
|
+
}
|
|
512
|
+
return text;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/runtime/resolve-attachments.ts
|
|
516
|
+
function isAttachmentAdapter(value) {
|
|
517
|
+
return typeof value === "object" && value !== null && "accept" in value && typeof value.add === "function" && typeof value.send === "function" && typeof value.remove === "function";
|
|
518
|
+
}
|
|
519
|
+
function resolveAttachmentAdapter(attachments, options = {}) {
|
|
520
|
+
const baseUrl = options.baseUrl ?? "/api";
|
|
521
|
+
const legacyUploadUrl = options.uploadUrl;
|
|
522
|
+
const legacyAccept = options.accept;
|
|
523
|
+
if (attachments === null) return void 0;
|
|
524
|
+
const legacyEnables = legacyUploadUrl !== void 0 || legacyAccept !== void 0;
|
|
525
|
+
if (attachments === void 0) {
|
|
526
|
+
if (!legacyEnables) return void 0;
|
|
527
|
+
return createDefaultAttachmentAdapter({
|
|
528
|
+
baseUrl,
|
|
529
|
+
fetch: options.fetch,
|
|
530
|
+
uploadUrl: legacyUploadUrl,
|
|
531
|
+
accept: legacyAccept
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (attachments === true) {
|
|
535
|
+
return createDefaultAttachmentAdapter({
|
|
536
|
+
baseUrl,
|
|
537
|
+
fetch: options.fetch,
|
|
538
|
+
uploadUrl: legacyUploadUrl,
|
|
539
|
+
accept: legacyAccept
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
if (isAttachmentAdapter(attachments)) return attachments;
|
|
543
|
+
const config = attachments;
|
|
544
|
+
return createDefaultAttachmentAdapter({
|
|
545
|
+
baseUrl,
|
|
546
|
+
fetch: options.fetch,
|
|
547
|
+
uploadUrl: config.uploadUrl ?? legacyUploadUrl,
|
|
548
|
+
accept: config.accept ?? legacyAccept
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
90
552
|
// src/runtime/provider.tsx
|
|
91
553
|
import { jsx } from "react/jsx-runtime";
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
554
|
+
function projectAttachment(attachment) {
|
|
555
|
+
const filename = attachment.name ?? "attachment";
|
|
556
|
+
const mimeType = attachment.contentType ?? "application/octet-stream";
|
|
557
|
+
if (attachment.type === "image") {
|
|
558
|
+
return {
|
|
559
|
+
id: attachment.id,
|
|
560
|
+
type: "image",
|
|
561
|
+
name: filename,
|
|
562
|
+
contentType: mimeType,
|
|
563
|
+
status: { type: "complete" },
|
|
564
|
+
content: [{ type: "image", image: attachment.dataUrl, filename }]
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
id: attachment.id,
|
|
569
|
+
type: attachment.type,
|
|
570
|
+
name: filename,
|
|
571
|
+
contentType: mimeType,
|
|
572
|
+
status: { type: "complete" },
|
|
573
|
+
content: [
|
|
574
|
+
{ type: "file", data: attachment.dataUrl, mimeType, filename }
|
|
575
|
+
]
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
var convertMessage = (message) => {
|
|
579
|
+
const content = message.content.map((part) => {
|
|
580
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
581
|
+
if (part.type === "thinking") return { type: "reasoning", text: part.text };
|
|
582
|
+
return {
|
|
583
|
+
type: "tool-call",
|
|
584
|
+
toolCallId: part.toolCallId,
|
|
585
|
+
toolName: part.toolName,
|
|
586
|
+
argsText: part.argsText,
|
|
587
|
+
...part.result !== void 0 ? { result: part.result } : {}
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
const attachments = message.attachments && message.attachments.length > 0 ? message.attachments.map(projectAttachment) : void 0;
|
|
591
|
+
return {
|
|
592
|
+
role: message.role,
|
|
593
|
+
content,
|
|
594
|
+
id: message.id,
|
|
595
|
+
...attachments ? { attachments } : {}
|
|
596
|
+
};
|
|
597
|
+
};
|
|
97
598
|
function findParentId(messages, beforeIndex) {
|
|
98
599
|
const slice = beforeIndex !== void 0 ? messages.slice(0, beforeIndex) : messages;
|
|
99
600
|
for (let i = slice.length - 1; i >= 0; i--) {
|
|
@@ -101,18 +602,18 @@ function findParentId(messages, beforeIndex) {
|
|
|
101
602
|
}
|
|
102
603
|
return null;
|
|
103
604
|
}
|
|
104
|
-
function isTopLevelStart(event) {
|
|
105
|
-
return event.type === "START" && typeof event.run_id === "string" && typeof event.path === "string" && !event.path.includes(".");
|
|
106
|
-
}
|
|
107
605
|
function getTextFromMessage(message) {
|
|
108
606
|
const part = message.content.find((c) => c.type === "text");
|
|
109
|
-
return part?.type === "text" ? part.text :
|
|
607
|
+
return part?.type === "text" ? part.text : "";
|
|
110
608
|
}
|
|
111
|
-
function
|
|
609
|
+
function getAttachmentsFromMessage(message) {
|
|
610
|
+
return message.attachments?.length ? message.attachments : void 0;
|
|
611
|
+
}
|
|
612
|
+
function useTimbalStream({
|
|
112
613
|
workforceId,
|
|
113
|
-
children,
|
|
114
614
|
baseUrl = "/api",
|
|
115
|
-
fetch: fetchFn
|
|
615
|
+
fetch: fetchFn,
|
|
616
|
+
debug = false
|
|
116
617
|
}) {
|
|
117
618
|
const [messages, setMessages] = useState([]);
|
|
118
619
|
const [isRunning, setIsRunning] = useState(false);
|
|
@@ -122,40 +623,41 @@ function TimbalRuntimeProvider({
|
|
|
122
623
|
useEffect(() => {
|
|
123
624
|
fetchFnRef.current = fetchFn ?? authFetch;
|
|
124
625
|
}, [fetchFn]);
|
|
626
|
+
const debugRef = useRef(debug);
|
|
627
|
+
useEffect(() => {
|
|
628
|
+
debugRef.current = debug;
|
|
629
|
+
}, [debug]);
|
|
125
630
|
useEffect(() => {
|
|
126
631
|
messagesRef.current = messages;
|
|
127
632
|
}, [messages]);
|
|
128
633
|
const streamAssistantResponse = useCallback(
|
|
129
|
-
async (input, userId, assistantId, parentId, signal) => {
|
|
130
|
-
const
|
|
131
|
-
const toolIndexById = /* @__PURE__ */ new Map();
|
|
132
|
-
const lastTextPart = () => {
|
|
133
|
-
const last = parts[parts.length - 1];
|
|
134
|
-
if (last?.type === "text") return last;
|
|
135
|
-
const next = { type: "text", text: "" };
|
|
136
|
-
parts.push(next);
|
|
137
|
-
return next;
|
|
138
|
-
};
|
|
634
|
+
async (input, attachments, userId, assistantId, parentId, signal) => {
|
|
635
|
+
const state = createReducerState();
|
|
139
636
|
const flush = () => {
|
|
140
637
|
setMessages(
|
|
141
|
-
(prev) => prev.map(
|
|
638
|
+
(prev) => prev.map(
|
|
639
|
+
(m) => m.id === assistantId ? { ...m, content: [...state.parts] } : m
|
|
640
|
+
)
|
|
142
641
|
);
|
|
143
642
|
};
|
|
144
643
|
const stampRunId = (runId) => {
|
|
145
644
|
setMessages(
|
|
146
|
-
(prev) => prev.map(
|
|
645
|
+
(prev) => prev.map(
|
|
646
|
+
(m) => m.id === userId || m.id === assistantId ? { ...m, runId } : m
|
|
647
|
+
)
|
|
147
648
|
);
|
|
148
649
|
};
|
|
149
650
|
try {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
651
|
+
const body = buildPromptBody({ input, attachments, parentId });
|
|
652
|
+
const res = await fetchFnRef.current(
|
|
653
|
+
`${baseUrl}/workforce/${workforceId}/stream`,
|
|
654
|
+
{
|
|
655
|
+
method: "POST",
|
|
656
|
+
headers: { "Content-Type": "application/json" },
|
|
657
|
+
body: JSON.stringify(body),
|
|
658
|
+
signal
|
|
659
|
+
}
|
|
660
|
+
);
|
|
159
661
|
if (!res.ok || !res.body) throw new Error(`Request failed: ${res.status}`);
|
|
160
662
|
const reader = res.body.getReader();
|
|
161
663
|
const decoder = new TextDecoder();
|
|
@@ -170,84 +672,34 @@ function TimbalRuntimeProvider({
|
|
|
170
672
|
for (const line of lines) {
|
|
171
673
|
const event = parseSSELine(line);
|
|
172
674
|
if (!event) continue;
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
stampRunId(capturedRunId);
|
|
675
|
+
if (debugRef.current) {
|
|
676
|
+
console.debug("[timbal]", event.type, event);
|
|
176
677
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
lastTextPart().text += item.text_delta;
|
|
183
|
-
flush();
|
|
184
|
-
} else if (item.type === "tool_use") {
|
|
185
|
-
const toolCallId = item.id || `tool-${crypto.randomUUID()}`;
|
|
186
|
-
const inputStr = typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? {});
|
|
187
|
-
parts.push({
|
|
188
|
-
type: "tool-call",
|
|
189
|
-
toolCallId,
|
|
190
|
-
toolName: item.name || "unknown",
|
|
191
|
-
argsText: inputStr
|
|
192
|
-
});
|
|
193
|
-
toolIndexById.set(toolCallId, parts.length - 1);
|
|
194
|
-
flush();
|
|
195
|
-
} else if (item.type === "tool_use_delta") {
|
|
196
|
-
const idx = toolIndexById.get(item.id);
|
|
197
|
-
if (idx !== void 0 && typeof item.input_delta === "string") {
|
|
198
|
-
parts[idx].argsText += item.input_delta;
|
|
199
|
-
flush();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
case "OUTPUT": {
|
|
205
|
-
const output = event.output;
|
|
206
|
-
if (!output) break;
|
|
207
|
-
if (typeof output === "object" && Array.isArray(output.content)) {
|
|
208
|
-
for (const block of output.content) {
|
|
209
|
-
if (block.type === "tool_use") {
|
|
210
|
-
const id = block.id || `tool-${crypto.randomUUID()}`;
|
|
211
|
-
const idx = toolIndexById.get(id);
|
|
212
|
-
if (idx !== void 0) {
|
|
213
|
-
parts[idx].result = "Tool executed";
|
|
214
|
-
} else {
|
|
215
|
-
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input ?? {});
|
|
216
|
-
parts.push({
|
|
217
|
-
type: "tool-call",
|
|
218
|
-
toolCallId: id,
|
|
219
|
-
toolName: block.name || "unknown",
|
|
220
|
-
argsText: inputStr,
|
|
221
|
-
result: "Tool executed"
|
|
222
|
-
});
|
|
223
|
-
toolIndexById.set(id, parts.length - 1);
|
|
224
|
-
}
|
|
225
|
-
} else if (block.type === "text" && typeof block.text === "string" && !lastTextPart().text) {
|
|
226
|
-
lastTextPart().text = block.text;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
flush();
|
|
230
|
-
} else if (parts.length === 0) {
|
|
231
|
-
const text = typeof output === "string" ? output : JSON.stringify(output);
|
|
232
|
-
parts.push({ type: "text", text });
|
|
233
|
-
flush();
|
|
234
|
-
}
|
|
235
|
-
break;
|
|
678
|
+
if (!capturedRunId) {
|
|
679
|
+
const runId = readTopLevelStartRunId(event);
|
|
680
|
+
if (runId) {
|
|
681
|
+
capturedRunId = runId;
|
|
682
|
+
stampRunId(runId);
|
|
236
683
|
}
|
|
237
684
|
}
|
|
685
|
+
const changed = reduceSseEvent(state, event);
|
|
686
|
+
if (changed) flush();
|
|
238
687
|
}
|
|
239
688
|
}
|
|
240
689
|
if (buffer.trim()) {
|
|
241
690
|
const event = parseSSELine(buffer);
|
|
242
|
-
if (event
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
691
|
+
if (event) {
|
|
692
|
+
if (debugRef.current) {
|
|
693
|
+
console.debug("[timbal]", event.type, event);
|
|
694
|
+
}
|
|
695
|
+
if (reduceSseEvent(state, event)) flush();
|
|
246
696
|
}
|
|
247
697
|
}
|
|
248
698
|
} catch (err) {
|
|
249
699
|
if (err.name !== "AbortError") {
|
|
250
|
-
if (parts.length === 0)
|
|
700
|
+
if (state.parts.length === 0) {
|
|
701
|
+
state.parts.push({ type: "text", text: "Something went wrong." });
|
|
702
|
+
}
|
|
251
703
|
flush();
|
|
252
704
|
}
|
|
253
705
|
} finally {
|
|
@@ -257,44 +709,46 @@ function TimbalRuntimeProvider({
|
|
|
257
709
|
},
|
|
258
710
|
[workforceId, baseUrl]
|
|
259
711
|
);
|
|
260
|
-
const
|
|
261
|
-
async (
|
|
262
|
-
const textPart = message.content.find((c) => c.type === "text");
|
|
263
|
-
if (!textPart || textPart.type !== "text") return;
|
|
264
|
-
const input = textPart.text;
|
|
712
|
+
const send = useCallback(
|
|
713
|
+
async (input, options) => {
|
|
265
714
|
const userId = crypto.randomUUID();
|
|
266
715
|
const assistantId = crypto.randomUUID();
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
716
|
+
const base = messagesRef.current;
|
|
717
|
+
const parentId = options?.parentId !== void 0 ? options.parentId : findParentId(base);
|
|
718
|
+
const userMessage = {
|
|
719
|
+
id: userId,
|
|
720
|
+
role: "user",
|
|
721
|
+
content: input ? [{ type: "text", text: input }] : [],
|
|
722
|
+
...options?.attachments && options.attachments.length > 0 ? { attachments: options.attachments } : {}
|
|
723
|
+
};
|
|
275
724
|
setMessages([
|
|
276
725
|
...base,
|
|
277
|
-
|
|
278
|
-
]);
|
|
279
|
-
setIsRunning(true);
|
|
280
|
-
setMessages((prev) => [
|
|
281
|
-
...prev,
|
|
726
|
+
userMessage,
|
|
282
727
|
{ id: assistantId, role: "assistant", content: [] }
|
|
283
728
|
]);
|
|
729
|
+
setIsRunning(true);
|
|
284
730
|
const controller = new AbortController();
|
|
285
731
|
abortRef.current = controller;
|
|
286
|
-
await streamAssistantResponse(
|
|
732
|
+
await streamAssistantResponse(
|
|
733
|
+
input,
|
|
734
|
+
options?.attachments,
|
|
735
|
+
userId,
|
|
736
|
+
assistantId,
|
|
737
|
+
parentId,
|
|
738
|
+
controller.signal
|
|
739
|
+
);
|
|
287
740
|
},
|
|
288
741
|
[streamAssistantResponse]
|
|
289
742
|
);
|
|
290
|
-
const
|
|
743
|
+
const reload = useCallback(
|
|
291
744
|
async (messageId) => {
|
|
292
745
|
const current = messagesRef.current;
|
|
293
746
|
const idx = messageId ? current.findIndex((m) => m.id === messageId) : current.length - 2;
|
|
294
747
|
const userMessage = idx >= 0 ? current[idx] : null;
|
|
295
748
|
if (!userMessage || userMessage.role !== "user") return;
|
|
296
749
|
const input = getTextFromMessage(userMessage);
|
|
297
|
-
|
|
750
|
+
const messageAttachments = getAttachmentsFromMessage(userMessage);
|
|
751
|
+
if (!input && !messageAttachments?.length) return;
|
|
298
752
|
const assistantId = crypto.randomUUID();
|
|
299
753
|
const parentId = findParentId(current, idx);
|
|
300
754
|
setMessages((prev) => [
|
|
@@ -304,25 +758,115 @@ function TimbalRuntimeProvider({
|
|
|
304
758
|
setIsRunning(true);
|
|
305
759
|
const controller = new AbortController();
|
|
306
760
|
abortRef.current = controller;
|
|
307
|
-
await streamAssistantResponse(
|
|
761
|
+
await streamAssistantResponse(
|
|
762
|
+
input,
|
|
763
|
+
messageAttachments,
|
|
764
|
+
userMessage.id,
|
|
765
|
+
assistantId,
|
|
766
|
+
parentId,
|
|
767
|
+
controller.signal
|
|
768
|
+
);
|
|
308
769
|
},
|
|
309
770
|
[streamAssistantResponse]
|
|
310
771
|
);
|
|
311
|
-
const
|
|
772
|
+
const cancel = useCallback(() => {
|
|
773
|
+
abortRef.current?.abort();
|
|
774
|
+
}, []);
|
|
775
|
+
const clear = useCallback(() => {
|
|
312
776
|
abortRef.current?.abort();
|
|
777
|
+
setMessages([]);
|
|
313
778
|
}, []);
|
|
779
|
+
return useMemo(
|
|
780
|
+
() => ({ messages, isRunning, send, reload, cancel, clear }),
|
|
781
|
+
[messages, isRunning, send, reload, cancel, clear]
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
function readTopLevelStartRunId(event) {
|
|
785
|
+
if (event.type === "START" && typeof event.run_id === "string" && typeof event.path === "string" && !event.path.includes(".")) {
|
|
786
|
+
return event.run_id;
|
|
787
|
+
}
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
var TimbalStreamContext = createContext(null);
|
|
791
|
+
function useTimbalRuntime() {
|
|
792
|
+
const ctx = useContext(TimbalStreamContext);
|
|
793
|
+
if (!ctx) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
"useTimbalRuntime must be used inside a <TimbalRuntimeProvider>."
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
return ctx;
|
|
799
|
+
}
|
|
800
|
+
function TimbalRuntimeProvider({
|
|
801
|
+
workforceId,
|
|
802
|
+
children,
|
|
803
|
+
baseUrl = "/api",
|
|
804
|
+
fetch: fetchFn,
|
|
805
|
+
attachments,
|
|
806
|
+
attachmentsUploadUrl,
|
|
807
|
+
attachmentsAccept,
|
|
808
|
+
debug
|
|
809
|
+
}) {
|
|
810
|
+
const stream = useTimbalStream({
|
|
811
|
+
workforceId,
|
|
812
|
+
baseUrl,
|
|
813
|
+
fetch: fetchFn,
|
|
814
|
+
debug
|
|
815
|
+
});
|
|
816
|
+
const attachmentAdapter = useMemo(
|
|
817
|
+
() => resolveAttachmentAdapter(attachments, {
|
|
818
|
+
baseUrl,
|
|
819
|
+
fetch: fetchFn,
|
|
820
|
+
uploadUrl: attachmentsUploadUrl,
|
|
821
|
+
accept: attachmentsAccept
|
|
822
|
+
}),
|
|
823
|
+
[attachments, attachmentsUploadUrl, attachmentsAccept, baseUrl, fetchFn]
|
|
824
|
+
);
|
|
825
|
+
const onNew = useCallback(
|
|
826
|
+
async (message) => {
|
|
827
|
+
const textPart = message.content.find((c) => c.type === "text");
|
|
828
|
+
const input = textPart && textPart.type === "text" ? textPart.text : "";
|
|
829
|
+
const auiAttachments = message.attachments;
|
|
830
|
+
const attachments2 = auiAttachments ? (await Promise.all(auiAttachments.map(extractAttachment))).filter((a) => a !== null) : [];
|
|
831
|
+
if (!input && attachments2.length === 0) return;
|
|
832
|
+
const parentId = message.parentId !== null && message.parentId !== void 0 ? findParentIdFromAuiParent(stream.messages, message.parentId) : void 0;
|
|
833
|
+
await stream.send(input, {
|
|
834
|
+
attachments: attachments2.length > 0 ? attachments2 : void 0,
|
|
835
|
+
...parentId !== void 0 ? { parentId } : {}
|
|
836
|
+
});
|
|
837
|
+
},
|
|
838
|
+
[stream]
|
|
839
|
+
);
|
|
840
|
+
const onReload = useCallback(
|
|
841
|
+
async (messageId) => {
|
|
842
|
+
await stream.reload(messageId);
|
|
843
|
+
},
|
|
844
|
+
[stream]
|
|
845
|
+
);
|
|
846
|
+
const onCancel = useCallback(async () => {
|
|
847
|
+
stream.cancel();
|
|
848
|
+
}, [stream]);
|
|
314
849
|
const runtime = useExternalStoreRuntime({
|
|
315
|
-
isRunning,
|
|
316
|
-
messages,
|
|
850
|
+
isRunning: stream.isRunning,
|
|
851
|
+
messages: stream.messages,
|
|
317
852
|
convertMessage,
|
|
318
853
|
onNew,
|
|
319
854
|
onEdit: onNew,
|
|
320
855
|
onReload,
|
|
321
|
-
onCancel
|
|
856
|
+
onCancel,
|
|
857
|
+
...attachmentAdapter ? { adapters: { attachments: attachmentAdapter } } : {}
|
|
322
858
|
});
|
|
323
|
-
return /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children });
|
|
859
|
+
return /* @__PURE__ */ jsx(TimbalStreamContext.Provider, { value: stream, children: /* @__PURE__ */ jsx(AssistantRuntimeProvider, { runtime, children }) });
|
|
860
|
+
}
|
|
861
|
+
function findParentIdFromAuiParent(messages, auiParentId) {
|
|
862
|
+
const idx = messages.findIndex((m) => m.id === auiParentId);
|
|
863
|
+
if (idx < 0) return null;
|
|
864
|
+
return findParentId(messages.slice(0, idx + 1));
|
|
324
865
|
}
|
|
325
866
|
|
|
867
|
+
// src/index.ts
|
|
868
|
+
import { parseSSELine as parseSSELine2 } from "@timbal-ai/timbal-sdk";
|
|
869
|
+
|
|
326
870
|
// src/components/attachment.tsx
|
|
327
871
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
328
872
|
import { XIcon as XIcon2, PlusIcon, FileText } from "lucide-react";
|
|
@@ -785,81 +1329,1123 @@ import {
|
|
|
785
1329
|
import remarkGfm from "remark-gfm";
|
|
786
1330
|
import remarkMath from "remark-math";
|
|
787
1331
|
import rehypeKatex from "rehype-katex";
|
|
788
|
-
import { memo, useState as
|
|
789
|
-
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
1332
|
+
import { memo, useState as useState5 } from "react";
|
|
1333
|
+
import { CheckIcon as CheckIcon2, CopyIcon } from "lucide-react";
|
|
790
1334
|
|
|
791
1335
|
// src/components/syntax-highlighter.tsx
|
|
792
|
-
import { useEffect as useEffect3, useState as
|
|
1336
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
793
1337
|
import { createHighlighterCore } from "shiki/core";
|
|
794
1338
|
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
import
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
import
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
import
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1339
|
+
|
|
1340
|
+
// src/artifacts/registry.tsx
|
|
1341
|
+
import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3 } from "react";
|
|
1342
|
+
|
|
1343
|
+
// src/artifacts/chart-artifact.tsx
|
|
1344
|
+
import { useMemo as useMemo2 } from "react";
|
|
1345
|
+
|
|
1346
|
+
// src/artifacts/artifact-card.tsx
|
|
1347
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1348
|
+
var ArtifactCard = ({ title, kind, className, bodyClassName, toolbar, children }) => {
|
|
1349
|
+
const hasHeader = Boolean(title || toolbar);
|
|
1350
|
+
return /* @__PURE__ */ jsxs5(
|
|
1351
|
+
"div",
|
|
1352
|
+
{
|
|
1353
|
+
className: cn(
|
|
1354
|
+
"aui-artifact-root my-3 overflow-hidden rounded-xl border border-border/60 bg-background shadow-sm",
|
|
1355
|
+
className
|
|
1356
|
+
),
|
|
1357
|
+
"data-artifact-kind": kind,
|
|
1358
|
+
children: [
|
|
1359
|
+
hasHeader && /* @__PURE__ */ jsxs5("div", { className: "aui-artifact-header flex items-center gap-2 border-b border-border/40 bg-muted/30 px-3 py-1.5", children: [
|
|
1360
|
+
title && /* @__PURE__ */ jsx8("span", { className: "aui-artifact-title flex-1 truncate text-xs font-semibold text-foreground/80", children: title }),
|
|
1361
|
+
!title && /* @__PURE__ */ jsx8("span", { className: "flex-1" }),
|
|
1362
|
+
toolbar
|
|
1363
|
+
] }),
|
|
1364
|
+
/* @__PURE__ */ jsx8("div", { className: cn("aui-artifact-body", bodyClassName), children })
|
|
1365
|
+
]
|
|
1366
|
+
}
|
|
1367
|
+
);
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
// src/artifacts/chart-artifact.tsx
|
|
1371
|
+
import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1372
|
+
var ChartArtifactView = ({
|
|
1373
|
+
artifact
|
|
1374
|
+
}) => {
|
|
1375
|
+
const { type: _t, chartType = "bar", data = [] } = artifact;
|
|
1376
|
+
const xKey = artifact.xKey ?? inferXKey(data);
|
|
1377
|
+
const dataKeys = useMemo2(() => {
|
|
1378
|
+
if (Array.isArray(artifact.dataKey)) return artifact.dataKey;
|
|
1379
|
+
if (typeof artifact.dataKey === "string") return [artifact.dataKey];
|
|
1380
|
+
return inferDataKeys(data, xKey);
|
|
1381
|
+
}, [artifact.dataKey, data, xKey]);
|
|
1382
|
+
return /* @__PURE__ */ jsx9(ArtifactCard, { title: artifact.title, kind: "chart", children: /* @__PURE__ */ jsxs6("div", { className: "aui-artifact-chart p-3", children: [
|
|
1383
|
+
/* @__PURE__ */ jsx9(
|
|
1384
|
+
ChartSvg,
|
|
1385
|
+
{
|
|
1386
|
+
chartType,
|
|
1387
|
+
data,
|
|
1388
|
+
xKey,
|
|
1389
|
+
dataKeys,
|
|
1390
|
+
unit: artifact.unit
|
|
1391
|
+
}
|
|
1392
|
+
),
|
|
1393
|
+
dataKeys.length > 1 && /* @__PURE__ */ jsx9(Legend, { dataKeys })
|
|
1394
|
+
] }) });
|
|
1395
|
+
};
|
|
1396
|
+
var COLORS = [
|
|
1397
|
+
"var(--primary, #6366f1)",
|
|
1398
|
+
"#22c55e",
|
|
1399
|
+
"#f59e0b",
|
|
1400
|
+
"#ef4444",
|
|
1401
|
+
"#06b6d4",
|
|
1402
|
+
"#a855f7"
|
|
1403
|
+
];
|
|
1404
|
+
var W = 600;
|
|
1405
|
+
var H = 240;
|
|
1406
|
+
var PAD = { top: 12, right: 16, bottom: 28, left: 36 };
|
|
1407
|
+
var ChartSvg = ({ chartType, data, xKey, dataKeys, unit }) => {
|
|
1408
|
+
if (data.length === 0 || dataKeys.length === 0) {
|
|
1409
|
+
return /* @__PURE__ */ jsx9(EmptyState, {});
|
|
1410
|
+
}
|
|
1411
|
+
if (chartType === "pie") {
|
|
1412
|
+
return /* @__PURE__ */ jsx9(PieChart, { data, xKey, dataKey: dataKeys[0] });
|
|
1413
|
+
}
|
|
1414
|
+
const innerW = W - PAD.left - PAD.right;
|
|
1415
|
+
const innerH = H - PAD.top - PAD.bottom;
|
|
1416
|
+
const all = dataKeys.flatMap((k) => data.map((d) => toNum(d[k])));
|
|
1417
|
+
const maxV = Math.max(0, ...all);
|
|
1418
|
+
const minV = Math.min(0, ...all);
|
|
1419
|
+
const range = maxV - minV || 1;
|
|
1420
|
+
const yScale = (v) => PAD.top + innerH - (v - minV) / range * innerH;
|
|
1421
|
+
const xCount = data.length;
|
|
1422
|
+
const xStep = xCount > 1 ? innerW / (xCount - 1) : innerW;
|
|
1423
|
+
const xPos = (i) => chartType === "bar" ? PAD.left + innerW * (i + 0.5) / xCount : PAD.left + i * xStep;
|
|
1424
|
+
const ticks = niceTicks(minV, maxV);
|
|
1425
|
+
return /* @__PURE__ */ jsxs6(
|
|
1426
|
+
"svg",
|
|
1427
|
+
{
|
|
1428
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
1429
|
+
className: "aui-artifact-chart-svg w-full",
|
|
1430
|
+
role: "img",
|
|
1431
|
+
"aria-label": "Chart",
|
|
1432
|
+
children: [
|
|
1433
|
+
ticks.map((t, i) => /* @__PURE__ */ jsxs6("g", { children: [
|
|
1434
|
+
/* @__PURE__ */ jsx9(
|
|
1435
|
+
"line",
|
|
1436
|
+
{
|
|
1437
|
+
x1: PAD.left,
|
|
1438
|
+
x2: W - PAD.right,
|
|
1439
|
+
y1: yScale(t),
|
|
1440
|
+
y2: yScale(t),
|
|
1441
|
+
stroke: "currentColor",
|
|
1442
|
+
strokeOpacity: 0.08
|
|
1443
|
+
}
|
|
1444
|
+
),
|
|
1445
|
+
/* @__PURE__ */ jsx9(
|
|
1446
|
+
"text",
|
|
1447
|
+
{
|
|
1448
|
+
x: PAD.left - 6,
|
|
1449
|
+
y: yScale(t),
|
|
1450
|
+
textAnchor: "end",
|
|
1451
|
+
dominantBaseline: "middle",
|
|
1452
|
+
className: "fill-muted-foreground text-[10px]",
|
|
1453
|
+
children: formatTick(t, unit)
|
|
1454
|
+
}
|
|
1455
|
+
)
|
|
1456
|
+
] }, i)),
|
|
1457
|
+
data.map((d, i) => /* @__PURE__ */ jsx9(
|
|
1458
|
+
"text",
|
|
1459
|
+
{
|
|
1460
|
+
x: xPos(i),
|
|
1461
|
+
y: H - PAD.bottom + 14,
|
|
1462
|
+
textAnchor: "middle",
|
|
1463
|
+
className: "fill-muted-foreground text-[10px]",
|
|
1464
|
+
children: String(d[xKey] ?? i)
|
|
1465
|
+
},
|
|
1466
|
+
i
|
|
1467
|
+
)),
|
|
1468
|
+
chartType === "bar" && renderBars({ data, dataKeys, xCount, xPos, yScale, minV, innerW }),
|
|
1469
|
+
chartType === "line" && dataKeys.map((k, ki) => /* @__PURE__ */ jsx9(
|
|
1470
|
+
Polyline,
|
|
1471
|
+
{
|
|
1472
|
+
data,
|
|
1473
|
+
dataKey: k,
|
|
1474
|
+
xPos,
|
|
1475
|
+
yScale,
|
|
1476
|
+
color: COLORS[ki % COLORS.length]
|
|
1477
|
+
},
|
|
1478
|
+
k
|
|
1479
|
+
)),
|
|
1480
|
+
chartType === "area" && dataKeys.map((k, ki) => /* @__PURE__ */ jsx9(
|
|
1481
|
+
Area,
|
|
1482
|
+
{
|
|
1483
|
+
data,
|
|
1484
|
+
dataKey: k,
|
|
1485
|
+
xPos,
|
|
1486
|
+
yScale,
|
|
1487
|
+
baseY: yScale(Math.max(0, minV)),
|
|
1488
|
+
color: COLORS[ki % COLORS.length]
|
|
1489
|
+
},
|
|
1490
|
+
k
|
|
1491
|
+
))
|
|
1492
|
+
]
|
|
1493
|
+
}
|
|
1494
|
+
);
|
|
1495
|
+
};
|
|
1496
|
+
function renderBars(args) {
|
|
1497
|
+
const { data, dataKeys, xCount, xPos, yScale, minV, innerW } = args;
|
|
1498
|
+
const groupWidth = innerW / xCount * 0.7;
|
|
1499
|
+
const barWidth = groupWidth / dataKeys.length;
|
|
1500
|
+
const baseY = yScale(Math.max(0, minV));
|
|
1501
|
+
return dataKeys.flatMap(
|
|
1502
|
+
(k, ki) => data.map((d, i) => {
|
|
1503
|
+
const v = toNum(d[k]);
|
|
1504
|
+
const y = yScale(v);
|
|
1505
|
+
const x = xPos(i) - groupWidth / 2 + ki * barWidth;
|
|
1506
|
+
const top = Math.min(y, baseY);
|
|
1507
|
+
const height = Math.abs(y - baseY);
|
|
1508
|
+
return /* @__PURE__ */ jsx9(
|
|
1509
|
+
"rect",
|
|
1510
|
+
{
|
|
1511
|
+
x,
|
|
1512
|
+
y: top,
|
|
1513
|
+
width: Math.max(1, barWidth - 2),
|
|
1514
|
+
height: Math.max(1, height),
|
|
1515
|
+
rx: 2,
|
|
1516
|
+
fill: COLORS[ki % COLORS.length]
|
|
1517
|
+
},
|
|
1518
|
+
`${k}-${i}`
|
|
1519
|
+
);
|
|
1520
|
+
})
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
var Polyline = ({ data, dataKey, xPos, yScale, color }) => {
|
|
1524
|
+
const points = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
|
|
1525
|
+
return /* @__PURE__ */ jsx9(
|
|
1526
|
+
"polyline",
|
|
1527
|
+
{
|
|
1528
|
+
points,
|
|
1529
|
+
fill: "none",
|
|
1530
|
+
stroke: color,
|
|
1531
|
+
strokeWidth: 2,
|
|
1532
|
+
strokeLinejoin: "round",
|
|
1533
|
+
strokeLinecap: "round"
|
|
1534
|
+
}
|
|
1535
|
+
);
|
|
1536
|
+
};
|
|
1537
|
+
var Area = ({ data, dataKey, xPos, yScale, baseY, color }) => {
|
|
1538
|
+
if (data.length === 0) return null;
|
|
1539
|
+
const top = data.map((d, i) => `${xPos(i)},${yScale(toNum(d[dataKey]))}`).join(" ");
|
|
1540
|
+
const path = `M ${xPos(0)},${baseY} L ${top} L ${xPos(data.length - 1)},${baseY} Z`;
|
|
1541
|
+
return /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1542
|
+
/* @__PURE__ */ jsx9("path", { d: path, fill: color, fillOpacity: 0.18 }),
|
|
1543
|
+
/* @__PURE__ */ jsx9(Polyline, { data, dataKey, xPos, yScale, color })
|
|
1544
|
+
] });
|
|
1545
|
+
};
|
|
1546
|
+
var PieChart = ({ data, xKey, dataKey }) => {
|
|
1547
|
+
const cx = W / 2;
|
|
1548
|
+
const cy = H / 2;
|
|
1549
|
+
const r = Math.min(W, H) / 2 - 16;
|
|
1550
|
+
const total = data.reduce((sum, d) => sum + toNum(d[dataKey]), 0) || 1;
|
|
1551
|
+
let acc = 0;
|
|
1552
|
+
return /* @__PURE__ */ jsx9(
|
|
1553
|
+
"svg",
|
|
1554
|
+
{
|
|
1555
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
1556
|
+
className: "aui-artifact-chart-svg w-full",
|
|
1557
|
+
role: "img",
|
|
1558
|
+
"aria-label": "Pie chart",
|
|
1559
|
+
children: data.map((d, i) => {
|
|
1560
|
+
const value = toNum(d[dataKey]);
|
|
1561
|
+
const start = acc / total * Math.PI * 2;
|
|
1562
|
+
acc += value;
|
|
1563
|
+
const end = acc / total * Math.PI * 2;
|
|
1564
|
+
return /* @__PURE__ */ jsx9(
|
|
1565
|
+
PieSlice,
|
|
1566
|
+
{
|
|
1567
|
+
cx,
|
|
1568
|
+
cy,
|
|
1569
|
+
r,
|
|
1570
|
+
start,
|
|
1571
|
+
end,
|
|
1572
|
+
color: COLORS[i % COLORS.length],
|
|
1573
|
+
label: String(d[xKey] ?? i)
|
|
1574
|
+
},
|
|
1575
|
+
i
|
|
1576
|
+
);
|
|
1577
|
+
})
|
|
1578
|
+
}
|
|
1579
|
+
);
|
|
1580
|
+
};
|
|
1581
|
+
var PieSlice = ({ cx, cy, r, start, end, color, label }) => {
|
|
1582
|
+
const x1 = cx + Math.sin(start) * r;
|
|
1583
|
+
const y1 = cy - Math.cos(start) * r;
|
|
1584
|
+
const x2 = cx + Math.sin(end) * r;
|
|
1585
|
+
const y2 = cy - Math.cos(end) * r;
|
|
1586
|
+
const large = end - start > Math.PI ? 1 : 0;
|
|
1587
|
+
const path = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
|
|
1588
|
+
const mid = (start + end) / 2;
|
|
1589
|
+
const lx = cx + Math.sin(mid) * (r * 0.65);
|
|
1590
|
+
const ly = cy - Math.cos(mid) * (r * 0.65);
|
|
1591
|
+
return /* @__PURE__ */ jsxs6("g", { children: [
|
|
1592
|
+
/* @__PURE__ */ jsx9("path", { d: path, fill: color, stroke: "var(--background, #fff)", strokeWidth: 1 }),
|
|
1593
|
+
/* @__PURE__ */ jsx9(
|
|
1594
|
+
"text",
|
|
1595
|
+
{
|
|
1596
|
+
x: lx,
|
|
1597
|
+
y: ly,
|
|
1598
|
+
textAnchor: "middle",
|
|
1599
|
+
dominantBaseline: "middle",
|
|
1600
|
+
className: "fill-white text-[10px] font-semibold",
|
|
1601
|
+
children: label
|
|
1602
|
+
}
|
|
1603
|
+
)
|
|
1604
|
+
] });
|
|
1605
|
+
};
|
|
1606
|
+
var Legend = ({ dataKeys }) => /* @__PURE__ */ jsx9("div", { className: "aui-artifact-chart-legend mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: dataKeys.map((k, i) => /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
1607
|
+
/* @__PURE__ */ jsx9(
|
|
1608
|
+
"span",
|
|
1609
|
+
{
|
|
1610
|
+
className: "inline-block size-2 rounded-sm",
|
|
1611
|
+
style: { background: COLORS[i % COLORS.length] },
|
|
1612
|
+
"aria-hidden": true
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
k
|
|
1616
|
+
] }, k)) });
|
|
1617
|
+
var EmptyState = () => /* @__PURE__ */ jsx9("div", { className: "aui-artifact-chart-empty flex h-32 items-center justify-center text-xs text-muted-foreground", children: "No data" });
|
|
1618
|
+
function inferXKey(data) {
|
|
1619
|
+
if (data.length === 0) return "x";
|
|
1620
|
+
for (const k of Object.keys(data[0])) {
|
|
1621
|
+
if (typeof data[0][k] !== "number") return k;
|
|
1622
|
+
}
|
|
1623
|
+
return Object.keys(data[0])[0] ?? "x";
|
|
1624
|
+
}
|
|
1625
|
+
function inferDataKeys(data, xKey) {
|
|
1626
|
+
if (data.length === 0) return [];
|
|
1627
|
+
return Object.keys(data[0]).filter(
|
|
1628
|
+
(k) => k !== xKey && typeof data[0][k] === "number"
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
function toNum(value) {
|
|
1632
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
1633
|
+
return Number.isFinite(n) ? n : 0;
|
|
1634
|
+
}
|
|
1635
|
+
function niceTicks(min, max, count = 4) {
|
|
1636
|
+
if (max === min) return [min];
|
|
1637
|
+
const range = max - min;
|
|
1638
|
+
const step = niceStep(range / count);
|
|
1639
|
+
const start = Math.floor(min / step) * step;
|
|
1640
|
+
const out = [];
|
|
1641
|
+
for (let v = start; v <= max + step / 2; v += step) {
|
|
1642
|
+
out.push(round(v));
|
|
1643
|
+
}
|
|
1644
|
+
return out;
|
|
1645
|
+
}
|
|
1646
|
+
function niceStep(raw) {
|
|
1647
|
+
const exp = Math.floor(Math.log10(Math.abs(raw))) || 0;
|
|
1648
|
+
const base = Math.pow(10, exp);
|
|
1649
|
+
const norm = raw / base;
|
|
1650
|
+
let nice = 1;
|
|
1651
|
+
if (norm >= 5) nice = 5;
|
|
1652
|
+
else if (norm >= 2) nice = 2;
|
|
1653
|
+
return nice * base;
|
|
1654
|
+
}
|
|
1655
|
+
function round(v) {
|
|
1656
|
+
return Math.round(v * 1e6) / 1e6;
|
|
1657
|
+
}
|
|
1658
|
+
function formatTick(v, unit) {
|
|
1659
|
+
const s = Math.abs(v) >= 1e3 ? `${(v / 1e3).toFixed(1)}k` : String(round(v));
|
|
1660
|
+
return unit ? `${s}${unit}` : s;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// src/artifacts/question-artifact.tsx
|
|
1664
|
+
import { useState as useState3 } from "react";
|
|
1665
|
+
import { useThreadRuntime } from "@assistant-ui/react";
|
|
1666
|
+
import { CheckIcon } from "lucide-react";
|
|
1667
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1668
|
+
var QuestionArtifactView = ({
|
|
1669
|
+
artifact
|
|
1670
|
+
}) => {
|
|
1671
|
+
const runtime = useThreadRuntime();
|
|
1672
|
+
const [selected, setSelected] = useState3([]);
|
|
1673
|
+
const [submitted, setSubmitted] = useState3(null);
|
|
1674
|
+
const isMulti = artifact.multi === true;
|
|
1675
|
+
const send = (labels) => {
|
|
1676
|
+
if (labels.length === 0) return;
|
|
1677
|
+
const text = labels.join(", ");
|
|
1678
|
+
setSubmitted(text);
|
|
1679
|
+
runtime.append({ role: "user", content: [{ type: "text", text }] });
|
|
1680
|
+
};
|
|
1681
|
+
const onPick = (option) => {
|
|
1682
|
+
if (submitted) return;
|
|
1683
|
+
if (!isMulti) {
|
|
1684
|
+
send([option.label]);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
setSelected(
|
|
1688
|
+
(prev) => prev.includes(option.id) ? prev.filter((id) => id !== option.id) : [...prev, option.id]
|
|
1689
|
+
);
|
|
1690
|
+
};
|
|
1691
|
+
const onConfirm = () => {
|
|
1692
|
+
const labels = artifact.options.filter((o) => selected.includes(o.id)).map((o) => o.label);
|
|
1693
|
+
send(labels);
|
|
1694
|
+
};
|
|
1695
|
+
return /* @__PURE__ */ jsx10(ArtifactCard, { kind: "question", children: /* @__PURE__ */ jsxs7("div", { className: "aui-artifact-question p-3", children: [
|
|
1696
|
+
artifact.prompt && /* @__PURE__ */ jsx10("p", { className: "aui-artifact-question-prompt mb-2 text-sm text-foreground/85", children: artifact.prompt }),
|
|
1697
|
+
/* @__PURE__ */ jsx10("div", { className: "aui-artifact-question-options flex flex-col gap-1.5", children: artifact.options.map((option) => {
|
|
1698
|
+
const isSelected = isMulti && selected.includes(option.id);
|
|
1699
|
+
const isDisabled = Boolean(submitted);
|
|
1700
|
+
return /* @__PURE__ */ jsxs7(
|
|
1701
|
+
"button",
|
|
1702
|
+
{
|
|
1703
|
+
type: "button",
|
|
1704
|
+
disabled: isDisabled,
|
|
1705
|
+
onClick: () => onPick(option),
|
|
1706
|
+
className: cn(
|
|
1707
|
+
"aui-artifact-question-option flex items-center gap-2 rounded-lg border px-3 py-2 text-left text-sm transition-colors",
|
|
1708
|
+
"border-border/60 hover:border-primary/40 hover:bg-muted/40",
|
|
1709
|
+
isSelected && "border-primary/60 bg-primary/5",
|
|
1710
|
+
isDisabled && "cursor-not-allowed opacity-60 hover:border-border/60 hover:bg-transparent"
|
|
1711
|
+
),
|
|
1712
|
+
children: [
|
|
1713
|
+
/* @__PURE__ */ jsx10(
|
|
1714
|
+
"span",
|
|
1715
|
+
{
|
|
1716
|
+
className: cn(
|
|
1717
|
+
"flex size-4 shrink-0 items-center justify-center rounded-full border",
|
|
1718
|
+
isSelected ? "border-primary bg-primary text-primary-foreground" : "border-border"
|
|
1719
|
+
),
|
|
1720
|
+
"aria-hidden": true,
|
|
1721
|
+
children: isSelected && /* @__PURE__ */ jsx10(CheckIcon, { className: "size-3" })
|
|
1722
|
+
}
|
|
1723
|
+
),
|
|
1724
|
+
/* @__PURE__ */ jsxs7("div", { className: "aui-artifact-question-option-text flex-1", children: [
|
|
1725
|
+
/* @__PURE__ */ jsx10("div", { className: "font-medium text-foreground/90", children: option.label }),
|
|
1726
|
+
option.description && /* @__PURE__ */ jsx10("div", { className: "text-xs text-muted-foreground", children: option.description })
|
|
1727
|
+
] })
|
|
1728
|
+
]
|
|
1729
|
+
},
|
|
1730
|
+
option.id
|
|
1731
|
+
);
|
|
1732
|
+
}) }),
|
|
1733
|
+
isMulti && !submitted && /* @__PURE__ */ jsx10("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx10(
|
|
1734
|
+
Button,
|
|
1735
|
+
{
|
|
1736
|
+
type: "button",
|
|
1737
|
+
size: "sm",
|
|
1738
|
+
disabled: selected.length === 0,
|
|
1739
|
+
onClick: onConfirm,
|
|
1740
|
+
children: "Confirm"
|
|
1741
|
+
}
|
|
1742
|
+
) }),
|
|
1743
|
+
submitted && /* @__PURE__ */ jsxs7("p", { className: "aui-artifact-question-submitted mt-2 text-xs text-muted-foreground", children: [
|
|
1744
|
+
"Sent: ",
|
|
1745
|
+
submitted
|
|
1746
|
+
] })
|
|
1747
|
+
] }) });
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1750
|
+
// src/artifacts/html-artifact.tsx
|
|
1751
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1752
|
+
var HtmlArtifactView = ({ artifact }) => {
|
|
1753
|
+
const sandboxed = artifact.sandboxed !== false;
|
|
1754
|
+
const sandbox = sandboxed ? "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-pointer-lock" : void 0;
|
|
1755
|
+
const height = artifact.height ?? "320px";
|
|
1756
|
+
return /* @__PURE__ */ jsx11(ArtifactCard, { title: artifact.title, kind: "html", children: /* @__PURE__ */ jsx11(
|
|
1757
|
+
"iframe",
|
|
1758
|
+
{
|
|
1759
|
+
title: artifact.title ?? "HTML artifact",
|
|
1760
|
+
srcDoc: artifact.content,
|
|
1761
|
+
sandbox,
|
|
1762
|
+
className: "aui-artifact-html w-full border-0 bg-background",
|
|
1763
|
+
style: { height }
|
|
1764
|
+
}
|
|
1765
|
+
) });
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// src/artifacts/json-artifact.tsx
|
|
1769
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1770
|
+
var JsonArtifactView = ({
|
|
1771
|
+
artifact
|
|
1772
|
+
}) => {
|
|
1773
|
+
const data = "data" in artifact ? artifact.data : artifact;
|
|
1774
|
+
const title = artifact.title;
|
|
1775
|
+
let body;
|
|
1776
|
+
try {
|
|
1777
|
+
body = JSON.stringify(data, null, 2);
|
|
1778
|
+
} catch {
|
|
1779
|
+
body = String(data);
|
|
1780
|
+
}
|
|
1781
|
+
return /* @__PURE__ */ jsx12(ArtifactCard, { title, kind: "json", children: /* @__PURE__ */ jsx12("pre", { className: "aui-artifact-json m-0 max-h-[420px] overflow-auto p-3 font-mono text-[12px] leading-relaxed text-foreground/85", children: body }) });
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
// src/artifacts/table-artifact.tsx
|
|
1785
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1786
|
+
var TableArtifactView = ({ artifact }) => {
|
|
1787
|
+
const rows = artifact.rows ?? [];
|
|
1788
|
+
const columns = artifact.columns ?? deriveColumns(rows);
|
|
1789
|
+
return /* @__PURE__ */ jsx13(ArtifactCard, { title: artifact.title, kind: "table", children: /* @__PURE__ */ jsx13("div", { className: "aui-artifact-table-wrap overflow-x-auto", children: /* @__PURE__ */ jsxs8("table", { className: "aui-artifact-table w-full border-collapse text-sm", children: [
|
|
1790
|
+
/* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsx13("tr", { className: "border-b border-border/40 bg-muted/20", children: columns.map((col) => /* @__PURE__ */ jsx13(
|
|
1791
|
+
"th",
|
|
1792
|
+
{
|
|
1793
|
+
className: "px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground",
|
|
1794
|
+
children: col.label ?? col.key
|
|
1795
|
+
},
|
|
1796
|
+
col.key
|
|
1797
|
+
)) }) }),
|
|
1798
|
+
/* @__PURE__ */ jsx13("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx13(
|
|
1799
|
+
"tr",
|
|
1800
|
+
{
|
|
1801
|
+
className: "border-b border-border/30 transition-colors last:border-b-0 hover:bg-muted/20",
|
|
1802
|
+
children: columns.map((col) => /* @__PURE__ */ jsx13(
|
|
1803
|
+
"td",
|
|
1804
|
+
{
|
|
1805
|
+
className: "px-3 py-2 align-top text-foreground/85",
|
|
1806
|
+
children: formatCell(row[col.key])
|
|
1807
|
+
},
|
|
1808
|
+
col.key
|
|
1809
|
+
))
|
|
1810
|
+
},
|
|
1811
|
+
i
|
|
1812
|
+
)) })
|
|
1813
|
+
] }) }) });
|
|
1814
|
+
};
|
|
1815
|
+
function deriveColumns(rows) {
|
|
1816
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1817
|
+
const cols = [];
|
|
1818
|
+
for (const row of rows) {
|
|
1819
|
+
for (const key of Object.keys(row)) {
|
|
1820
|
+
if (!seen.has(key)) {
|
|
1821
|
+
seen.add(key);
|
|
1822
|
+
cols.push({ key });
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return cols;
|
|
1827
|
+
}
|
|
1828
|
+
function formatCell(value) {
|
|
1829
|
+
if (value === null || value === void 0) return "";
|
|
1830
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
1831
|
+
return String(value);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/artifacts/ui/ui-artifact.tsx
|
|
1835
|
+
import { useReducer } from "react";
|
|
1836
|
+
|
|
1837
|
+
// src/artifacts/ui/types.ts
|
|
1838
|
+
function isUiBinding(value) {
|
|
1839
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.$bind === "string";
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// src/artifacts/ui/state.ts
|
|
1843
|
+
function uiStateReducer(state, action) {
|
|
1844
|
+
switch (action.type) {
|
|
1845
|
+
case "set":
|
|
1846
|
+
return setPath(state, action.path, action.value);
|
|
1847
|
+
case "toggle": {
|
|
1848
|
+
const current = getPath(state, action.path);
|
|
1849
|
+
return setPath(state, action.path, !current);
|
|
1850
|
+
}
|
|
1851
|
+
case "replace":
|
|
1852
|
+
return action.state;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
function getPath(state, path) {
|
|
1856
|
+
if (!path) return void 0;
|
|
1857
|
+
const parts = path.split(".");
|
|
1858
|
+
let cursor = state;
|
|
1859
|
+
for (const part of parts) {
|
|
1860
|
+
if (typeof cursor !== "object" || cursor === null) return void 0;
|
|
1861
|
+
cursor = cursor[part];
|
|
1862
|
+
}
|
|
1863
|
+
return cursor;
|
|
1864
|
+
}
|
|
1865
|
+
function setPath(state, path, value) {
|
|
1866
|
+
if (!path) return state;
|
|
1867
|
+
const parts = path.split(".");
|
|
1868
|
+
const next = { ...state };
|
|
1869
|
+
let cursor = next;
|
|
1870
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1871
|
+
const key = parts[i];
|
|
1872
|
+
const child = cursor[key];
|
|
1873
|
+
const cloned = typeof child === "object" && child !== null && !Array.isArray(child) ? { ...child } : {};
|
|
1874
|
+
cursor[key] = cloned;
|
|
1875
|
+
cursor = cloned;
|
|
1876
|
+
}
|
|
1877
|
+
cursor[parts[parts.length - 1]] = value;
|
|
1878
|
+
return next;
|
|
1879
|
+
}
|
|
1880
|
+
function resolveBindable(value, state) {
|
|
1881
|
+
if (isUiBinding(value)) {
|
|
1882
|
+
return getPath(state, value.$bind);
|
|
1883
|
+
}
|
|
1884
|
+
return value;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// src/artifacts/ui/registry.tsx
|
|
1888
|
+
import {
|
|
1889
|
+
createContext as createContext2,
|
|
1890
|
+
useContext as useContext2
|
|
1891
|
+
} from "react";
|
|
1892
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1893
|
+
var UiStateContext = createContext2({});
|
|
1894
|
+
var UiDispatchContext = createContext2(() => {
|
|
1895
|
+
});
|
|
1896
|
+
var UiStateProvider = ({ state, dispatch, children }) => /* @__PURE__ */ jsx14(UiStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx14(UiDispatchContext.Provider, { value: dispatch, children }) });
|
|
1897
|
+
function useUiState() {
|
|
1898
|
+
return useContext2(UiStateContext);
|
|
1899
|
+
}
|
|
1900
|
+
function useUiDispatch() {
|
|
1901
|
+
return useContext2(UiDispatchContext);
|
|
1902
|
+
}
|
|
1903
|
+
var UiEventContext = createContext2(
|
|
1904
|
+
null
|
|
1905
|
+
);
|
|
1906
|
+
var UiEventProvider = ({ onEvent, children }) => /* @__PURE__ */ jsx14(UiEventContext.Provider, { value: onEvent, children });
|
|
1907
|
+
function useUiEventEmitter() {
|
|
1908
|
+
return useContext2(UiEventContext);
|
|
1909
|
+
}
|
|
1910
|
+
var UiCustomNodeRegistryContext = createContext2({});
|
|
1911
|
+
var UiCustomNodeRegistryProvider = ({ renderers, children }) => /* @__PURE__ */ jsx14(UiCustomNodeRegistryContext.Provider, { value: renderers, children });
|
|
1912
|
+
function useUiCustomNodeRegistry() {
|
|
1913
|
+
return useContext2(UiCustomNodeRegistryContext);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/artifacts/ui/nodes.tsx
|
|
1917
|
+
import { useCallback as useCallback2 } from "react";
|
|
1918
|
+
import { motion } from "motion/react";
|
|
1919
|
+
import { useThreadRuntime as useThreadRuntime2 } from "@assistant-ui/react";
|
|
1920
|
+
import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1921
|
+
var UiNodeView = ({ node }) => {
|
|
1922
|
+
switch (node.kind) {
|
|
1923
|
+
case "box":
|
|
1924
|
+
return /* @__PURE__ */ jsx15(BoxNode, { node });
|
|
1925
|
+
case "text":
|
|
1926
|
+
return /* @__PURE__ */ jsx15(TextNode, { node });
|
|
1927
|
+
case "heading":
|
|
1928
|
+
return /* @__PURE__ */ jsx15(HeadingNode, { node });
|
|
1929
|
+
case "badge":
|
|
1930
|
+
return /* @__PURE__ */ jsx15(BadgeNode, { node });
|
|
1931
|
+
case "button":
|
|
1932
|
+
return /* @__PURE__ */ jsx15(ButtonNode, { node });
|
|
1933
|
+
case "toggle":
|
|
1934
|
+
return /* @__PURE__ */ jsx15(ToggleNode, { node });
|
|
1935
|
+
case "slider":
|
|
1936
|
+
return /* @__PURE__ */ jsx15(SliderNode, { node });
|
|
1937
|
+
case "tooltip":
|
|
1938
|
+
return /* @__PURE__ */ jsx15(TooltipNode, { node });
|
|
1939
|
+
case "draggable":
|
|
1940
|
+
return /* @__PURE__ */ jsx15(DraggableNode, { node });
|
|
1941
|
+
case "custom":
|
|
1942
|
+
return /* @__PURE__ */ jsx15(CustomNode, { node });
|
|
1943
|
+
default:
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
function useActionRunner() {
|
|
1948
|
+
const state = useUiState();
|
|
1949
|
+
const dispatch = useUiDispatch();
|
|
1950
|
+
const runtime = useThreadRuntime2();
|
|
1951
|
+
const emit = useUiEventEmitter();
|
|
1952
|
+
return useCallback2(
|
|
1953
|
+
(actions) => {
|
|
1954
|
+
if (!actions) return;
|
|
1955
|
+
const list = Array.isArray(actions) ? actions : [actions];
|
|
1956
|
+
for (const action of list) {
|
|
1957
|
+
switch (action.kind) {
|
|
1958
|
+
case "message": {
|
|
1959
|
+
const text = resolveBindable(action.text, state);
|
|
1960
|
+
if (typeof text === "string" && text.length > 0) {
|
|
1961
|
+
runtime?.append({
|
|
1962
|
+
role: "user",
|
|
1963
|
+
content: [{ type: "text", text }]
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
break;
|
|
1967
|
+
}
|
|
1968
|
+
case "set": {
|
|
1969
|
+
const value = resolveBindable(action.value, state);
|
|
1970
|
+
dispatch({ type: "set", path: action.path, value });
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
case "toggle": {
|
|
1974
|
+
dispatch({ type: "toggle", path: action.path });
|
|
1975
|
+
break;
|
|
1976
|
+
}
|
|
1977
|
+
case "emit": {
|
|
1978
|
+
emit?.({ name: action.name, payload: action.payload });
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
},
|
|
1984
|
+
[state, dispatch, runtime, emit]
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
var ALIGN_CLS = {
|
|
1988
|
+
start: "items-start",
|
|
1989
|
+
center: "items-center",
|
|
1990
|
+
end: "items-end",
|
|
1991
|
+
stretch: "items-stretch"
|
|
1992
|
+
};
|
|
1993
|
+
var JUSTIFY_CLS = {
|
|
1994
|
+
start: "justify-start",
|
|
1995
|
+
center: "justify-center",
|
|
1996
|
+
end: "justify-end",
|
|
1997
|
+
between: "justify-between",
|
|
1998
|
+
around: "justify-around"
|
|
1999
|
+
};
|
|
2000
|
+
var BoxNode = ({ node }) => {
|
|
2001
|
+
const dir = node.direction ?? "col";
|
|
2002
|
+
return /* @__PURE__ */ jsx15(
|
|
2003
|
+
"div",
|
|
2004
|
+
{
|
|
2005
|
+
className: cn(
|
|
2006
|
+
"aui-ui-box flex",
|
|
2007
|
+
dir === "col" ? "flex-col" : "flex-row",
|
|
2008
|
+
node.wrap && "flex-wrap",
|
|
2009
|
+
node.align && ALIGN_CLS[node.align],
|
|
2010
|
+
node.justify && JUSTIFY_CLS[node.justify],
|
|
2011
|
+
node.className
|
|
2012
|
+
),
|
|
2013
|
+
style: {
|
|
2014
|
+
gap: node.gap !== void 0 ? `${node.gap * 0.25}rem` : void 0,
|
|
2015
|
+
padding: node.padding !== void 0 ? `${node.padding * 0.25}rem` : void 0
|
|
2016
|
+
},
|
|
2017
|
+
children: node.children?.map((child, i) => /* @__PURE__ */ jsx15(UiNodeView, { node: child }, child.id ?? i))
|
|
2018
|
+
}
|
|
2019
|
+
);
|
|
2020
|
+
};
|
|
2021
|
+
var TEXT_SIZE = {
|
|
2022
|
+
xs: "text-xs",
|
|
2023
|
+
sm: "text-sm",
|
|
2024
|
+
base: "text-base",
|
|
2025
|
+
lg: "text-lg"
|
|
2026
|
+
};
|
|
2027
|
+
var TEXT_WEIGHT = {
|
|
2028
|
+
normal: "font-normal",
|
|
2029
|
+
medium: "font-medium",
|
|
2030
|
+
semibold: "font-semibold",
|
|
2031
|
+
bold: "font-bold"
|
|
2032
|
+
};
|
|
2033
|
+
var TextNode = ({ node }) => {
|
|
2034
|
+
const state = useUiState();
|
|
2035
|
+
const value = resolveBindable(node.value, state);
|
|
2036
|
+
return /* @__PURE__ */ jsx15(
|
|
2037
|
+
"span",
|
|
2038
|
+
{
|
|
2039
|
+
className: cn(
|
|
2040
|
+
"aui-ui-text",
|
|
2041
|
+
node.muted && "text-muted-foreground",
|
|
2042
|
+
node.size && TEXT_SIZE[node.size],
|
|
2043
|
+
node.weight && TEXT_WEIGHT[node.weight],
|
|
2044
|
+
node.className
|
|
2045
|
+
),
|
|
2046
|
+
children: value === void 0 || value === null ? "" : String(value)
|
|
2047
|
+
}
|
|
2048
|
+
);
|
|
2049
|
+
};
|
|
2050
|
+
var HEADING_CLS = {
|
|
2051
|
+
1: "text-2xl",
|
|
2052
|
+
2: "text-xl",
|
|
2053
|
+
3: "text-lg",
|
|
2054
|
+
4: "text-base"
|
|
2055
|
+
};
|
|
2056
|
+
var HeadingNode = ({ node }) => {
|
|
2057
|
+
const state = useUiState();
|
|
2058
|
+
const value = String(resolveBindable(node.value, state) ?? "");
|
|
2059
|
+
const level = node.level ?? 2;
|
|
2060
|
+
const cls = cn(
|
|
2061
|
+
"aui-ui-heading font-semibold text-foreground",
|
|
2062
|
+
HEADING_CLS[level],
|
|
2063
|
+
node.className
|
|
2064
|
+
);
|
|
2065
|
+
switch (level) {
|
|
2066
|
+
case 1:
|
|
2067
|
+
return /* @__PURE__ */ jsx15("h1", { className: cls, children: value });
|
|
2068
|
+
case 2:
|
|
2069
|
+
return /* @__PURE__ */ jsx15("h2", { className: cls, children: value });
|
|
2070
|
+
case 3:
|
|
2071
|
+
return /* @__PURE__ */ jsx15("h3", { className: cls, children: value });
|
|
2072
|
+
case 4:
|
|
2073
|
+
return /* @__PURE__ */ jsx15("h4", { className: cls, children: value });
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
var BADGE_TONE = {
|
|
2077
|
+
default: "bg-muted text-foreground",
|
|
2078
|
+
primary: "bg-primary/10 text-primary",
|
|
2079
|
+
success: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
|
|
2080
|
+
warn: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
|
|
2081
|
+
danger: "bg-destructive/10 text-destructive"
|
|
2082
|
+
};
|
|
2083
|
+
var BadgeNode = ({ node }) => {
|
|
2084
|
+
const state = useUiState();
|
|
2085
|
+
const value = String(resolveBindable(node.value, state) ?? "");
|
|
2086
|
+
return /* @__PURE__ */ jsx15(
|
|
2087
|
+
"span",
|
|
2088
|
+
{
|
|
2089
|
+
className: cn(
|
|
2090
|
+
"aui-ui-badge inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
|
|
2091
|
+
BADGE_TONE[node.tone ?? "default"],
|
|
2092
|
+
node.className
|
|
2093
|
+
),
|
|
2094
|
+
children: value
|
|
2095
|
+
}
|
|
2096
|
+
);
|
|
2097
|
+
};
|
|
2098
|
+
var ButtonNode = ({ node }) => {
|
|
2099
|
+
const state = useUiState();
|
|
2100
|
+
const run = useActionRunner();
|
|
2101
|
+
const label = String(resolveBindable(node.label, state) ?? "");
|
|
2102
|
+
const disabled = node.disabled !== void 0 ? Boolean(resolveBindable(node.disabled, state)) : false;
|
|
2103
|
+
return /* @__PURE__ */ jsx15(
|
|
2104
|
+
Button,
|
|
2105
|
+
{
|
|
2106
|
+
variant: node.variant ?? "default",
|
|
2107
|
+
size: node.size ?? "default",
|
|
2108
|
+
disabled,
|
|
2109
|
+
className: node.className,
|
|
2110
|
+
onClick: () => run(node.onClick),
|
|
2111
|
+
children: label
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
};
|
|
2115
|
+
var ToggleNode = ({ node }) => {
|
|
2116
|
+
const state = useUiState();
|
|
2117
|
+
const dispatch = useUiDispatch();
|
|
2118
|
+
const run = useActionRunner();
|
|
2119
|
+
const value = Boolean(getPath(state, node.binding));
|
|
2120
|
+
const label = node.label ? String(resolveBindable(node.label, state) ?? "") : null;
|
|
2121
|
+
const onToggle = () => {
|
|
2122
|
+
dispatch({ type: "toggle", path: node.binding });
|
|
2123
|
+
run(node.onChange);
|
|
2124
|
+
};
|
|
2125
|
+
return /* @__PURE__ */ jsxs9(
|
|
2126
|
+
"label",
|
|
2127
|
+
{
|
|
2128
|
+
className: cn(
|
|
2129
|
+
"aui-ui-toggle inline-flex cursor-pointer items-center gap-2 text-sm select-none",
|
|
2130
|
+
node.className
|
|
2131
|
+
),
|
|
2132
|
+
children: [
|
|
2133
|
+
/* @__PURE__ */ jsx15(
|
|
2134
|
+
"button",
|
|
2135
|
+
{
|
|
2136
|
+
type: "button",
|
|
2137
|
+
role: "switch",
|
|
2138
|
+
"aria-checked": value,
|
|
2139
|
+
onClick: onToggle,
|
|
2140
|
+
className: cn(
|
|
2141
|
+
"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors",
|
|
2142
|
+
value ? "border-primary bg-primary" : "border-border bg-muted hover:bg-muted/80"
|
|
2143
|
+
),
|
|
2144
|
+
children: /* @__PURE__ */ jsx15(
|
|
2145
|
+
"span",
|
|
2146
|
+
{
|
|
2147
|
+
className: cn(
|
|
2148
|
+
"inline-block size-4 transform rounded-full bg-background shadow transition-transform",
|
|
2149
|
+
value ? "translate-x-4" : "translate-x-0.5"
|
|
2150
|
+
),
|
|
2151
|
+
"aria-hidden": true
|
|
2152
|
+
}
|
|
2153
|
+
)
|
|
2154
|
+
}
|
|
2155
|
+
),
|
|
2156
|
+
label && /* @__PURE__ */ jsx15("span", { className: "text-foreground/85", children: label })
|
|
2157
|
+
]
|
|
2158
|
+
}
|
|
2159
|
+
);
|
|
2160
|
+
};
|
|
2161
|
+
var SliderNode = ({ node }) => {
|
|
2162
|
+
const state = useUiState();
|
|
2163
|
+
const dispatch = useUiDispatch();
|
|
2164
|
+
const run = useActionRunner();
|
|
2165
|
+
const min = node.min ?? 0;
|
|
2166
|
+
const max = node.max ?? 100;
|
|
2167
|
+
const step = node.step ?? 1;
|
|
2168
|
+
const raw = getPath(state, node.binding);
|
|
2169
|
+
const value = typeof raw === "number" ? raw : min;
|
|
2170
|
+
const showValue = node.showValue ?? true;
|
|
2171
|
+
const label = node.label ? String(resolveBindable(node.label, state) ?? "") : null;
|
|
2172
|
+
const onChange = (e) => {
|
|
2173
|
+
const next = Number(e.target.value);
|
|
2174
|
+
dispatch({ type: "set", path: node.binding, value: next });
|
|
2175
|
+
};
|
|
2176
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn("aui-ui-slider flex flex-col gap-1", node.className), children: [
|
|
2177
|
+
(label || showValue) && /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
|
|
2178
|
+
label && /* @__PURE__ */ jsx15("span", { children: label }),
|
|
2179
|
+
showValue && /* @__PURE__ */ jsx15("span", { className: "font-mono", children: value })
|
|
2180
|
+
] }),
|
|
2181
|
+
/* @__PURE__ */ jsx15(
|
|
2182
|
+
"input",
|
|
2183
|
+
{
|
|
2184
|
+
type: "range",
|
|
2185
|
+
min,
|
|
2186
|
+
max,
|
|
2187
|
+
step,
|
|
2188
|
+
value,
|
|
2189
|
+
onChange,
|
|
2190
|
+
onMouseUp: () => run(node.onChange),
|
|
2191
|
+
onKeyUp: (e) => {
|
|
2192
|
+
if (e.key === "Enter" || e.key === " ") run(node.onChange);
|
|
2193
|
+
},
|
|
2194
|
+
onTouchEnd: () => run(node.onChange),
|
|
2195
|
+
className: "aui-ui-slider-input h-1.5 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary"
|
|
2196
|
+
}
|
|
2197
|
+
)
|
|
2198
|
+
] });
|
|
2199
|
+
};
|
|
2200
|
+
var TooltipNode = ({ node }) => {
|
|
2201
|
+
const state = useUiState();
|
|
2202
|
+
const content = String(resolveBindable(node.content, state) ?? "");
|
|
2203
|
+
return /* @__PURE__ */ jsx15(TooltipProvider, { children: /* @__PURE__ */ jsxs9(Tooltip, { children: [
|
|
2204
|
+
/* @__PURE__ */ jsx15(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx15("span", { className: cn("aui-ui-tooltip-trigger inline-flex", node.className), children: /* @__PURE__ */ jsx15(UiNodeView, { node: node.child }) }) }),
|
|
2205
|
+
/* @__PURE__ */ jsx15(TooltipContent, { side: node.side ?? "top", children: content })
|
|
2206
|
+
] }) });
|
|
2207
|
+
};
|
|
2208
|
+
var DraggableNode = ({ node }) => {
|
|
2209
|
+
const run = useActionRunner();
|
|
2210
|
+
const snapBack = node.snapBack ?? true;
|
|
2211
|
+
const axis = node.axis ?? "both";
|
|
2212
|
+
const dragProp = axis === "both" ? true : axis;
|
|
2213
|
+
return /* @__PURE__ */ jsx15(
|
|
2214
|
+
motion.div,
|
|
2215
|
+
{
|
|
2216
|
+
drag: dragProp,
|
|
2217
|
+
dragMomentum: false,
|
|
2218
|
+
dragSnapToOrigin: snapBack,
|
|
2219
|
+
whileDrag: { scale: 1.02, cursor: "grabbing" },
|
|
2220
|
+
onDragEnd: () => run(node.onDragEnd),
|
|
2221
|
+
className: cn(
|
|
2222
|
+
"aui-ui-draggable inline-block cursor-grab touch-none",
|
|
2223
|
+
node.className
|
|
2224
|
+
),
|
|
2225
|
+
children: /* @__PURE__ */ jsx15(UiNodeView, { node: node.child })
|
|
2226
|
+
}
|
|
2227
|
+
);
|
|
2228
|
+
};
|
|
2229
|
+
var CustomNode = ({ node }) => {
|
|
2230
|
+
const state = useUiState();
|
|
2231
|
+
const registry = useUiCustomNodeRegistry();
|
|
2232
|
+
const Renderer = registry[node.name];
|
|
2233
|
+
if (!Renderer) return null;
|
|
2234
|
+
const resolvedProps = resolveProps(node.props ?? {}, state);
|
|
2235
|
+
const children = node.children?.map((child, i) => /* @__PURE__ */ jsx15(UiNodeView, { node: child }, child.id ?? i));
|
|
2236
|
+
return /* @__PURE__ */ jsx15(Renderer, { props: resolvedProps, children });
|
|
2237
|
+
};
|
|
2238
|
+
function resolveProps(props, state) {
|
|
2239
|
+
const out = {};
|
|
2240
|
+
for (const [k, v] of Object.entries(props)) {
|
|
2241
|
+
out[k] = resolveBindable(v, state);
|
|
2242
|
+
}
|
|
2243
|
+
return out;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// src/artifacts/ui/ui-artifact.tsx
|
|
2247
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2248
|
+
var UiArtifactView = ({ artifact }) => {
|
|
2249
|
+
const [state, dispatch] = useReducer(
|
|
2250
|
+
uiStateReducer,
|
|
2251
|
+
artifact.initialState ?? {}
|
|
2252
|
+
);
|
|
2253
|
+
return /* @__PURE__ */ jsx16(ArtifactCard, { title: artifact.title, kind: "ui", children: /* @__PURE__ */ jsx16(UiStateProvider, { state, dispatch, children: /* @__PURE__ */ jsx16("div", { className: "aui-ui-root p-3", children: /* @__PURE__ */ jsx16(UiNodeView, { node: artifact.root }) }) }) });
|
|
2254
|
+
};
|
|
2255
|
+
|
|
2256
|
+
// src/artifacts/registry.tsx
|
|
2257
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2258
|
+
var defaultArtifactRenderers = {
|
|
2259
|
+
chart: ChartArtifactView,
|
|
2260
|
+
question: QuestionArtifactView,
|
|
2261
|
+
html: HtmlArtifactView,
|
|
2262
|
+
json: JsonArtifactView,
|
|
2263
|
+
table: TableArtifactView,
|
|
2264
|
+
ui: UiArtifactView
|
|
2265
|
+
};
|
|
2266
|
+
var ArtifactRegistryContext = createContext3(
|
|
2267
|
+
defaultArtifactRenderers
|
|
2268
|
+
);
|
|
2269
|
+
var ArtifactRegistryProvider = ({ renderers, override, children }) => {
|
|
2270
|
+
const merged = useMemo3(() => {
|
|
2271
|
+
if (!renderers) return defaultArtifactRenderers;
|
|
2272
|
+
if (override) return renderers;
|
|
2273
|
+
return { ...defaultArtifactRenderers, ...renderers };
|
|
2274
|
+
}, [renderers, override]);
|
|
2275
|
+
return /* @__PURE__ */ jsx17(ArtifactRegistryContext.Provider, { value: merged, children });
|
|
2276
|
+
};
|
|
2277
|
+
function useArtifactRegistry() {
|
|
2278
|
+
return useContext3(ArtifactRegistryContext);
|
|
2279
|
+
}
|
|
2280
|
+
var ArtifactView = ({ artifact }) => {
|
|
2281
|
+
const registry = useArtifactRegistry();
|
|
2282
|
+
const Renderer = registry[artifact.type] ?? registry.json;
|
|
2283
|
+
if (!Renderer) return null;
|
|
2284
|
+
return /* @__PURE__ */ jsx17(Renderer, { artifact });
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
// src/artifacts/parse.ts
|
|
2288
|
+
var ARTIFACT_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
|
|
2289
|
+
"timbal-artifact",
|
|
2290
|
+
"timbal"
|
|
2291
|
+
]);
|
|
2292
|
+
function isArtifactFenceLanguage(language) {
|
|
2293
|
+
return typeof language === "string" && ARTIFACT_FENCE_LANGUAGES.has(language);
|
|
2294
|
+
}
|
|
2295
|
+
function tryParseStructuredText(text) {
|
|
2296
|
+
const trimmed = text.trim();
|
|
2297
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
2298
|
+
try {
|
|
2299
|
+
return JSON.parse(trimmed);
|
|
2300
|
+
} catch {
|
|
2301
|
+
}
|
|
2302
|
+
try {
|
|
2303
|
+
const asJson = trimmed.replace(/\bTrue\b/g, "true").replace(/\bFalse\b/g, "false").replace(/\bNone\b/g, "null").replace(/'/g, '"');
|
|
2304
|
+
return JSON.parse(asJson);
|
|
2305
|
+
} catch {
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
function parseArtifactFromToolResult(result) {
|
|
2310
|
+
if (result === void 0 || result === null) return null;
|
|
2311
|
+
if (typeof result === "string") {
|
|
2312
|
+
const parsed = tryParseStructuredText(result);
|
|
2313
|
+
if (parsed === null) return null;
|
|
2314
|
+
return parseArtifactFromToolResult(parsed);
|
|
2315
|
+
}
|
|
2316
|
+
if (Array.isArray(result)) {
|
|
2317
|
+
for (const item of result) {
|
|
2318
|
+
if (typeof item === "object" && item !== null && "text" in item) {
|
|
2319
|
+
const text = item.text;
|
|
2320
|
+
if (typeof text === "string") {
|
|
2321
|
+
const fromText = parseArtifactFromToolResult(text);
|
|
2322
|
+
if (fromText) return fromText;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
return null;
|
|
2327
|
+
}
|
|
2328
|
+
if (typeof result === "object") {
|
|
2329
|
+
const obj = result;
|
|
2330
|
+
if (obj.type === "text" && typeof obj.text === "string") {
|
|
2331
|
+
return parseArtifactFromToolResult(obj.text);
|
|
2332
|
+
}
|
|
2333
|
+
if (obj.type === "thinking" && typeof obj.thinking === "string") {
|
|
2334
|
+
return parseArtifactFromToolResult(obj.thinking);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
return isArtifact(result) ? result : null;
|
|
2338
|
+
}
|
|
2339
|
+
var FENCE_RE = /```(?:timbal-artifact|timbal)\s*\n([\s\S]*?)\n```/g;
|
|
2340
|
+
function findMarkdownArtifacts(markdown) {
|
|
2341
|
+
const matches = [];
|
|
2342
|
+
FENCE_RE.lastIndex = 0;
|
|
2343
|
+
let m;
|
|
2344
|
+
while ((m = FENCE_RE.exec(markdown)) !== null) {
|
|
2345
|
+
const raw = m[0];
|
|
2346
|
+
const body = m[1];
|
|
2347
|
+
try {
|
|
2348
|
+
const parsed = JSON.parse(body);
|
|
2349
|
+
if (isArtifact(parsed)) {
|
|
2350
|
+
matches.push({
|
|
2351
|
+
artifact: parsed,
|
|
2352
|
+
raw,
|
|
2353
|
+
start: m.index,
|
|
2354
|
+
end: m.index + raw.length
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
} catch {
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
return matches;
|
|
2361
|
+
}
|
|
2362
|
+
function splitMarkdownByArtifacts(markdown) {
|
|
2363
|
+
const matches = findMarkdownArtifacts(markdown);
|
|
2364
|
+
if (matches.length === 0) return [{ kind: "text", text: markdown }];
|
|
2365
|
+
const segments = [];
|
|
2366
|
+
let cursor = 0;
|
|
2367
|
+
for (const match of matches) {
|
|
2368
|
+
if (match.start > cursor) {
|
|
2369
|
+
segments.push({ kind: "text", text: markdown.slice(cursor, match.start) });
|
|
2370
|
+
}
|
|
2371
|
+
segments.push({ kind: "artifact", artifact: match.artifact });
|
|
2372
|
+
cursor = match.end;
|
|
2373
|
+
}
|
|
2374
|
+
if (cursor < markdown.length) {
|
|
2375
|
+
segments.push({ kind: "text", text: markdown.slice(cursor) });
|
|
2376
|
+
}
|
|
2377
|
+
return segments;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// src/components/syntax-highlighter.tsx
|
|
2381
|
+
import langJavascript from "shiki/langs/javascript.mjs";
|
|
2382
|
+
import langTypescript from "shiki/langs/typescript.mjs";
|
|
2383
|
+
import langPython from "shiki/langs/python.mjs";
|
|
2384
|
+
import langHtml from "shiki/langs/html.mjs";
|
|
2385
|
+
import langCss from "shiki/langs/css.mjs";
|
|
2386
|
+
import langJson from "shiki/langs/json.mjs";
|
|
2387
|
+
import langBash from "shiki/langs/bash.mjs";
|
|
2388
|
+
import langMarkdown from "shiki/langs/markdown.mjs";
|
|
2389
|
+
import langJsx from "shiki/langs/jsx.mjs";
|
|
2390
|
+
import langTsx from "shiki/langs/tsx.mjs";
|
|
2391
|
+
import langSql from "shiki/langs/sql.mjs";
|
|
2392
|
+
import langYaml from "shiki/langs/yaml.mjs";
|
|
2393
|
+
import langRust from "shiki/langs/rust.mjs";
|
|
2394
|
+
import langGo from "shiki/langs/go.mjs";
|
|
2395
|
+
import langJava from "shiki/langs/java.mjs";
|
|
2396
|
+
import langC from "shiki/langs/c.mjs";
|
|
2397
|
+
import langCpp from "shiki/langs/cpp.mjs";
|
|
2398
|
+
import themeVitesseDark from "shiki/themes/vitesse-dark.mjs";
|
|
2399
|
+
import themeVitesseLight from "shiki/themes/vitesse-light.mjs";
|
|
2400
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
2401
|
+
var SHIKI_THEME_DARK = "vitesse-dark";
|
|
2402
|
+
var SHIKI_THEME_LIGHT = "vitesse-light";
|
|
2403
|
+
var highlighterPromise = null;
|
|
2404
|
+
function getHighlighter() {
|
|
2405
|
+
if (!highlighterPromise) {
|
|
2406
|
+
highlighterPromise = createHighlighterCore({
|
|
2407
|
+
themes: [themeVitesseDark, themeVitesseLight],
|
|
2408
|
+
langs: [
|
|
2409
|
+
langJavascript,
|
|
2410
|
+
langTypescript,
|
|
2411
|
+
langPython,
|
|
2412
|
+
langHtml,
|
|
2413
|
+
langCss,
|
|
2414
|
+
langJson,
|
|
2415
|
+
langBash,
|
|
2416
|
+
langMarkdown,
|
|
2417
|
+
langJsx,
|
|
2418
|
+
langTsx,
|
|
2419
|
+
langSql,
|
|
2420
|
+
langYaml,
|
|
2421
|
+
langRust,
|
|
2422
|
+
langGo,
|
|
2423
|
+
langJava,
|
|
2424
|
+
langC,
|
|
2425
|
+
langCpp
|
|
2426
|
+
],
|
|
2427
|
+
engine: createJavaScriptRegexEngine()
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
return highlighterPromise;
|
|
2431
|
+
}
|
|
2432
|
+
getHighlighter();
|
|
2433
|
+
var ShikiSyntaxHighlighter = ({
|
|
2434
|
+
components: { Pre, Code: Code2 },
|
|
2435
|
+
language,
|
|
2436
|
+
code
|
|
2437
|
+
}) => {
|
|
2438
|
+
const [html, setHtml] = useState4(null);
|
|
2439
|
+
useEffect3(() => {
|
|
2440
|
+
let cancelled = false;
|
|
2441
|
+
(async () => {
|
|
2442
|
+
try {
|
|
2443
|
+
const highlighter = await getHighlighter();
|
|
2444
|
+
const loadedLangs = highlighter.getLoadedLanguages();
|
|
2445
|
+
if (!loadedLangs.includes(language)) {
|
|
2446
|
+
if (!cancelled) setHtml(null);
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
863
2449
|
const result = highlighter.codeToHtml(code, {
|
|
864
2450
|
lang: language,
|
|
865
2451
|
themes: {
|
|
@@ -876,8 +2462,17 @@ var ShikiSyntaxHighlighter = ({
|
|
|
876
2462
|
cancelled = true;
|
|
877
2463
|
};
|
|
878
2464
|
}, [code, language]);
|
|
2465
|
+
if (isArtifactFenceLanguage(language)) {
|
|
2466
|
+
try {
|
|
2467
|
+
const parsed = JSON.parse(code);
|
|
2468
|
+
if (isArtifact(parsed)) {
|
|
2469
|
+
return /* @__PURE__ */ jsx18(ArtifactView, { artifact: parsed });
|
|
2470
|
+
}
|
|
2471
|
+
} catch {
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
879
2474
|
if (html) {
|
|
880
|
-
return /* @__PURE__ */
|
|
2475
|
+
return /* @__PURE__ */ jsx18(
|
|
881
2476
|
"div",
|
|
882
2477
|
{
|
|
883
2478
|
className: "shiki-wrapper [&>pre]:!m-0 [&>pre]:!rounded-t-none [&>pre]:!rounded-b-lg [&>pre]:!border [&>pre]:!border-t-0 [&>pre]:!border-border/50 [&>pre]:!p-3 [&>pre]:!text-xs [&>pre]:!leading-relaxed [&>pre]:overflow-x-auto",
|
|
@@ -885,14 +2480,14 @@ var ShikiSyntaxHighlighter = ({
|
|
|
885
2480
|
}
|
|
886
2481
|
);
|
|
887
2482
|
}
|
|
888
|
-
return /* @__PURE__ */
|
|
2483
|
+
return /* @__PURE__ */ jsx18(Pre, { children: /* @__PURE__ */ jsx18(Code2, { children: code }) });
|
|
889
2484
|
};
|
|
890
2485
|
var syntax_highlighter_default = ShikiSyntaxHighlighter;
|
|
891
2486
|
|
|
892
2487
|
// src/components/markdown-text.tsx
|
|
893
|
-
import { jsx as
|
|
2488
|
+
import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
894
2489
|
var MarkdownTextImpl = () => {
|
|
895
|
-
return /* @__PURE__ */
|
|
2490
|
+
return /* @__PURE__ */ jsx19(
|
|
896
2491
|
MarkdownTextPrimitive,
|
|
897
2492
|
{
|
|
898
2493
|
remarkPlugins: [remarkGfm, remarkMath],
|
|
@@ -908,24 +2503,25 @@ var MarkdownTextImpl = () => {
|
|
|
908
2503
|
var MarkdownText = memo(MarkdownTextImpl);
|
|
909
2504
|
var CodeHeader = ({ language, code }) => {
|
|
910
2505
|
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
2506
|
+
if (isArtifactFenceLanguage(language)) return null;
|
|
911
2507
|
const onCopy = () => {
|
|
912
2508
|
if (!code || isCopied) return;
|
|
913
2509
|
copyToClipboard(code);
|
|
914
2510
|
};
|
|
915
|
-
return /* @__PURE__ */
|
|
916
|
-
/* @__PURE__ */
|
|
917
|
-
/* @__PURE__ */
|
|
2511
|
+
return /* @__PURE__ */ jsxs10("div", { className: "aui-code-header flex items-center justify-between rounded-t-lg border border-b-0 border-border/50 bg-zinc-100 px-4 py-2 dark:bg-zinc-800/80", children: [
|
|
2512
|
+
/* @__PURE__ */ jsxs10("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-wide text-muted-foreground/80 uppercase", children: [
|
|
2513
|
+
/* @__PURE__ */ jsx19("span", { className: "inline-block h-2 w-2 rounded-full bg-primary/40" }),
|
|
918
2514
|
language
|
|
919
2515
|
] }),
|
|
920
|
-
/* @__PURE__ */
|
|
2516
|
+
/* @__PURE__ */ jsxs10(
|
|
921
2517
|
TooltipIconButton,
|
|
922
2518
|
{
|
|
923
2519
|
tooltip: isCopied ? "Copied!" : "Copy",
|
|
924
2520
|
onClick: onCopy,
|
|
925
2521
|
className: "transition-colors hover:text-foreground",
|
|
926
2522
|
children: [
|
|
927
|
-
!isCopied && /* @__PURE__ */
|
|
928
|
-
isCopied && /* @__PURE__ */
|
|
2523
|
+
!isCopied && /* @__PURE__ */ jsx19(CopyIcon, { className: "h-3.5 w-3.5" }),
|
|
2524
|
+
isCopied && /* @__PURE__ */ jsx19(CheckIcon2, { className: "h-3.5 w-3.5 text-emerald-500" })
|
|
929
2525
|
]
|
|
930
2526
|
}
|
|
931
2527
|
)
|
|
@@ -934,7 +2530,7 @@ var CodeHeader = ({ language, code }) => {
|
|
|
934
2530
|
var useCopyToClipboard = ({
|
|
935
2531
|
copiedDuration = 3e3
|
|
936
2532
|
} = {}) => {
|
|
937
|
-
const [isCopied, setIsCopied] =
|
|
2533
|
+
const [isCopied, setIsCopied] = useState5(false);
|
|
938
2534
|
const copyToClipboard = (value) => {
|
|
939
2535
|
if (!value) return;
|
|
940
2536
|
navigator.clipboard.writeText(value).then(() => {
|
|
@@ -945,7 +2541,7 @@ var useCopyToClipboard = ({
|
|
|
945
2541
|
return { isCopied, copyToClipboard };
|
|
946
2542
|
};
|
|
947
2543
|
var defaultComponents = memoizeMarkdownComponents({
|
|
948
|
-
h1: ({ className, ...props }) => /* @__PURE__ */
|
|
2544
|
+
h1: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
949
2545
|
"h1",
|
|
950
2546
|
{
|
|
951
2547
|
className: cn(
|
|
@@ -955,7 +2551,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
955
2551
|
...props
|
|
956
2552
|
}
|
|
957
2553
|
),
|
|
958
|
-
h2: ({ className, ...props }) => /* @__PURE__ */
|
|
2554
|
+
h2: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
959
2555
|
"h2",
|
|
960
2556
|
{
|
|
961
2557
|
className: cn(
|
|
@@ -965,7 +2561,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
965
2561
|
...props
|
|
966
2562
|
}
|
|
967
2563
|
),
|
|
968
|
-
h3: ({ className, ...props }) => /* @__PURE__ */
|
|
2564
|
+
h3: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
969
2565
|
"h3",
|
|
970
2566
|
{
|
|
971
2567
|
className: cn(
|
|
@@ -975,7 +2571,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
975
2571
|
...props
|
|
976
2572
|
}
|
|
977
2573
|
),
|
|
978
|
-
h4: ({ className, ...props }) => /* @__PURE__ */
|
|
2574
|
+
h4: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
979
2575
|
"h4",
|
|
980
2576
|
{
|
|
981
2577
|
className: cn(
|
|
@@ -985,7 +2581,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
985
2581
|
...props
|
|
986
2582
|
}
|
|
987
2583
|
),
|
|
988
|
-
h5: ({ className, ...props }) => /* @__PURE__ */
|
|
2584
|
+
h5: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
989
2585
|
"h5",
|
|
990
2586
|
{
|
|
991
2587
|
className: cn(
|
|
@@ -995,7 +2591,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
995
2591
|
...props
|
|
996
2592
|
}
|
|
997
2593
|
),
|
|
998
|
-
h6: ({ className, ...props }) => /* @__PURE__ */
|
|
2594
|
+
h6: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
999
2595
|
"h6",
|
|
1000
2596
|
{
|
|
1001
2597
|
className: cn(
|
|
@@ -1005,7 +2601,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1005
2601
|
...props
|
|
1006
2602
|
}
|
|
1007
2603
|
),
|
|
1008
|
-
p: ({ className, ...props }) => /* @__PURE__ */
|
|
2604
|
+
p: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1009
2605
|
"p",
|
|
1010
2606
|
{
|
|
1011
2607
|
className: cn(
|
|
@@ -1015,7 +2611,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1015
2611
|
...props
|
|
1016
2612
|
}
|
|
1017
2613
|
),
|
|
1018
|
-
a: ({ className, ...props }) => /* @__PURE__ */
|
|
2614
|
+
a: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1019
2615
|
"a",
|
|
1020
2616
|
{
|
|
1021
2617
|
className: cn(
|
|
@@ -1027,7 +2623,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1027
2623
|
...props
|
|
1028
2624
|
}
|
|
1029
2625
|
),
|
|
1030
|
-
blockquote: ({ className, ...props }) => /* @__PURE__ */
|
|
2626
|
+
blockquote: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1031
2627
|
"blockquote",
|
|
1032
2628
|
{
|
|
1033
2629
|
className: cn(
|
|
@@ -1037,7 +2633,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1037
2633
|
...props
|
|
1038
2634
|
}
|
|
1039
2635
|
),
|
|
1040
|
-
ul: ({ className, ...props }) => /* @__PURE__ */
|
|
2636
|
+
ul: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1041
2637
|
"ul",
|
|
1042
2638
|
{
|
|
1043
2639
|
className: cn(
|
|
@@ -1047,7 +2643,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1047
2643
|
...props
|
|
1048
2644
|
}
|
|
1049
2645
|
),
|
|
1050
|
-
ol: ({ className, ...props }) => /* @__PURE__ */
|
|
2646
|
+
ol: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1051
2647
|
"ol",
|
|
1052
2648
|
{
|
|
1053
2649
|
className: cn(
|
|
@@ -1057,7 +2653,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1057
2653
|
...props
|
|
1058
2654
|
}
|
|
1059
2655
|
),
|
|
1060
|
-
hr: ({ className, ...props }) => /* @__PURE__ */
|
|
2656
|
+
hr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1061
2657
|
"hr",
|
|
1062
2658
|
{
|
|
1063
2659
|
className: cn(
|
|
@@ -1067,14 +2663,14 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1067
2663
|
...props
|
|
1068
2664
|
}
|
|
1069
2665
|
),
|
|
1070
|
-
table: ({ className, ...props }) => /* @__PURE__ */
|
|
2666
|
+
table: ({ className, ...props }) => /* @__PURE__ */ jsx19("div", { className: "my-4 w-full overflow-x-auto rounded-lg border border-border/50", children: /* @__PURE__ */ jsx19(
|
|
1071
2667
|
"table",
|
|
1072
2668
|
{
|
|
1073
2669
|
className: cn("aui-md-table w-full border-collapse text-sm", className),
|
|
1074
2670
|
...props
|
|
1075
2671
|
}
|
|
1076
2672
|
) }),
|
|
1077
|
-
th: ({ className, ...props }) => /* @__PURE__ */
|
|
2673
|
+
th: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1078
2674
|
"th",
|
|
1079
2675
|
{
|
|
1080
2676
|
className: cn(
|
|
@@ -1084,7 +2680,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1084
2680
|
...props
|
|
1085
2681
|
}
|
|
1086
2682
|
),
|
|
1087
|
-
td: ({ className, ...props }) => /* @__PURE__ */
|
|
2683
|
+
td: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1088
2684
|
"td",
|
|
1089
2685
|
{
|
|
1090
2686
|
className: cn(
|
|
@@ -1094,7 +2690,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1094
2690
|
...props
|
|
1095
2691
|
}
|
|
1096
2692
|
),
|
|
1097
|
-
tr: ({ className, ...props }) => /* @__PURE__ */
|
|
2693
|
+
tr: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1098
2694
|
"tr",
|
|
1099
2695
|
{
|
|
1100
2696
|
className: cn(
|
|
@@ -1104,8 +2700,8 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1104
2700
|
...props
|
|
1105
2701
|
}
|
|
1106
2702
|
),
|
|
1107
|
-
li: ({ className, ...props }) => /* @__PURE__ */
|
|
1108
|
-
sup: ({ className, ...props }) => /* @__PURE__ */
|
|
2703
|
+
li: ({ className, ...props }) => /* @__PURE__ */ jsx19("li", { className: cn("aui-md-li leading-[1.7]", className), ...props }),
|
|
2704
|
+
sup: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1109
2705
|
"sup",
|
|
1110
2706
|
{
|
|
1111
2707
|
className: cn(
|
|
@@ -1115,7 +2711,7 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1115
2711
|
...props
|
|
1116
2712
|
}
|
|
1117
2713
|
),
|
|
1118
|
-
pre: ({ className, ...props }) => /* @__PURE__ */
|
|
2714
|
+
pre: ({ className, ...props }) => /* @__PURE__ */ jsx19(
|
|
1119
2715
|
"pre",
|
|
1120
2716
|
{
|
|
1121
2717
|
className: cn(
|
|
@@ -1124,122 +2720,394 @@ var defaultComponents = memoizeMarkdownComponents({
|
|
|
1124
2720
|
),
|
|
1125
2721
|
...props
|
|
1126
2722
|
}
|
|
1127
|
-
),
|
|
1128
|
-
code: function Code({ className, ...props }) {
|
|
1129
|
-
const isCodeBlock = useIsMarkdownCodeBlock();
|
|
1130
|
-
return /* @__PURE__ */
|
|
1131
|
-
"code",
|
|
2723
|
+
),
|
|
2724
|
+
code: function Code({ className, ...props }) {
|
|
2725
|
+
const isCodeBlock = useIsMarkdownCodeBlock();
|
|
2726
|
+
return /* @__PURE__ */ jsx19(
|
|
2727
|
+
"code",
|
|
2728
|
+
{
|
|
2729
|
+
className: cn(
|
|
2730
|
+
!isCodeBlock && "aui-md-inline-code rounded-[5px] border border-border/60 bg-muted/60 px-[0.4em] py-[0.15em] font-mono text-[0.85em] font-medium text-foreground/90 dark:bg-muted/40",
|
|
2731
|
+
className
|
|
2732
|
+
),
|
|
2733
|
+
...props
|
|
2734
|
+
}
|
|
2735
|
+
);
|
|
2736
|
+
},
|
|
2737
|
+
strong: ({ className, ...props }) => /* @__PURE__ */ jsx19("strong", { className: cn("font-semibold text-foreground", className), ...props }),
|
|
2738
|
+
em: ({ className, ...props }) => /* @__PURE__ */ jsx19("em", { className: cn("italic", className), ...props }),
|
|
2739
|
+
CodeHeader
|
|
2740
|
+
});
|
|
2741
|
+
|
|
2742
|
+
// src/components/tool-fallback.tsx
|
|
2743
|
+
import { memo as memo3, useState as useState6 } from "react";
|
|
2744
|
+
import { ChevronDownIcon, WrenchIcon } from "lucide-react";
|
|
2745
|
+
|
|
2746
|
+
// src/ui/shimmer.tsx
|
|
2747
|
+
import { motion as motion2 } from "motion/react";
|
|
2748
|
+
import {
|
|
2749
|
+
memo as memo2,
|
|
2750
|
+
useMemo as useMemo4
|
|
2751
|
+
} from "react";
|
|
2752
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2753
|
+
var ShimmerComponent = ({
|
|
2754
|
+
children,
|
|
2755
|
+
as: Component = "p",
|
|
2756
|
+
className,
|
|
2757
|
+
duration = 2,
|
|
2758
|
+
spread = 2
|
|
2759
|
+
}) => {
|
|
2760
|
+
const MotionComponent = motion2.create(
|
|
2761
|
+
Component
|
|
2762
|
+
);
|
|
2763
|
+
const dynamicSpread = useMemo4(
|
|
2764
|
+
() => (children?.length ?? 0) * spread,
|
|
2765
|
+
[children, spread]
|
|
2766
|
+
);
|
|
2767
|
+
return /* @__PURE__ */ jsx20(
|
|
2768
|
+
MotionComponent,
|
|
2769
|
+
{
|
|
2770
|
+
animate: { backgroundPosition: "0% center" },
|
|
2771
|
+
className: cn(
|
|
2772
|
+
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
|
|
2773
|
+
"[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",
|
|
2774
|
+
className
|
|
2775
|
+
),
|
|
2776
|
+
initial: { backgroundPosition: "100% center" },
|
|
2777
|
+
style: {
|
|
2778
|
+
"--spread": `${dynamicSpread}px`,
|
|
2779
|
+
backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
|
|
2780
|
+
},
|
|
2781
|
+
transition: {
|
|
2782
|
+
repeat: Number.POSITIVE_INFINITY,
|
|
2783
|
+
duration,
|
|
2784
|
+
ease: "linear"
|
|
2785
|
+
},
|
|
2786
|
+
children
|
|
2787
|
+
}
|
|
2788
|
+
);
|
|
2789
|
+
};
|
|
2790
|
+
var Shimmer = memo2(ShimmerComponent);
|
|
2791
|
+
|
|
2792
|
+
// src/components/tool-fallback.tsx
|
|
2793
|
+
import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2794
|
+
var ToolFallbackImpl = ({
|
|
2795
|
+
toolName,
|
|
2796
|
+
argsText,
|
|
2797
|
+
result,
|
|
2798
|
+
status
|
|
2799
|
+
}) => {
|
|
2800
|
+
const isRunning = status?.type === "running";
|
|
2801
|
+
const isError = status?.type === "incomplete" && status.reason !== "cancelled";
|
|
2802
|
+
if (isRunning) {
|
|
2803
|
+
return /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-running flex items-center gap-2 py-1 text-sm text-muted-foreground", children: [
|
|
2804
|
+
/* @__PURE__ */ jsx21(WrenchIcon, { className: "size-4" }),
|
|
2805
|
+
/* @__PURE__ */ jsx21(Shimmer, { as: "span", duration: 1.8, spread: 2.5, children: `Using tool: ${toolName}` })
|
|
2806
|
+
] });
|
|
2807
|
+
}
|
|
2808
|
+
return /* @__PURE__ */ jsx21(
|
|
2809
|
+
ToolPanel,
|
|
2810
|
+
{
|
|
2811
|
+
toolName,
|
|
2812
|
+
argsText,
|
|
2813
|
+
result,
|
|
2814
|
+
isError
|
|
2815
|
+
}
|
|
2816
|
+
);
|
|
2817
|
+
};
|
|
2818
|
+
var ToolPanel = ({ toolName, argsText, result, isError }) => {
|
|
2819
|
+
const [open, setOpen] = useState6(false);
|
|
2820
|
+
return /* @__PURE__ */ jsxs11(
|
|
2821
|
+
"div",
|
|
2822
|
+
{
|
|
2823
|
+
className: cn(
|
|
2824
|
+
"aui-tool-fallback-root my-2 overflow-hidden rounded-lg border border-border/60 bg-muted/30 text-sm",
|
|
2825
|
+
isError && "border-destructive/50 bg-destructive/5"
|
|
2826
|
+
),
|
|
2827
|
+
children: [
|
|
2828
|
+
/* @__PURE__ */ jsxs11(
|
|
2829
|
+
"button",
|
|
2830
|
+
{
|
|
2831
|
+
type: "button",
|
|
2832
|
+
onClick: () => setOpen((v) => !v),
|
|
2833
|
+
className: "aui-tool-fallback-header flex w-full items-center gap-2 px-3 py-2 text-left text-muted-foreground transition-colors hover:bg-muted/50",
|
|
2834
|
+
"aria-expanded": open,
|
|
2835
|
+
children: [
|
|
2836
|
+
/* @__PURE__ */ jsx21(WrenchIcon, { className: "size-3.5" }),
|
|
2837
|
+
/* @__PURE__ */ jsx21("span", { className: "aui-tool-fallback-name flex-1 truncate font-mono text-xs font-medium text-foreground/80", children: toolName }),
|
|
2838
|
+
isError && /* @__PURE__ */ jsx21("span", { className: "aui-tool-fallback-status text-xs font-medium text-destructive", children: "error" }),
|
|
2839
|
+
/* @__PURE__ */ jsx21(
|
|
2840
|
+
ChevronDownIcon,
|
|
2841
|
+
{
|
|
2842
|
+
className: cn(
|
|
2843
|
+
"size-3.5 shrink-0 transition-transform",
|
|
2844
|
+
open && "rotate-180"
|
|
2845
|
+
)
|
|
2846
|
+
}
|
|
2847
|
+
)
|
|
2848
|
+
]
|
|
2849
|
+
}
|
|
2850
|
+
),
|
|
2851
|
+
open && /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-body grid gap-2 border-t border-border/40 bg-background/50 px-3 py-2.5 text-xs", children: [
|
|
2852
|
+
argsText && argsText !== "{}" && /* @__PURE__ */ jsx21(Section, { label: "Input", value: argsText }),
|
|
2853
|
+
result !== void 0 && result !== null && /* @__PURE__ */ jsx21(Section, { label: "Output", value: formatResult(result) })
|
|
2854
|
+
] })
|
|
2855
|
+
]
|
|
2856
|
+
}
|
|
2857
|
+
);
|
|
2858
|
+
};
|
|
2859
|
+
var Section = ({ label, value }) => /* @__PURE__ */ jsxs11("div", { className: "aui-tool-fallback-section", children: [
|
|
2860
|
+
/* @__PURE__ */ jsx21("div", { className: "aui-tool-fallback-section-label mb-0.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: label }),
|
|
2861
|
+
/* @__PURE__ */ jsx21("pre", { className: "aui-tool-fallback-section-value overflow-x-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed text-foreground/85", children: value })
|
|
2862
|
+
] });
|
|
2863
|
+
function formatResult(result) {
|
|
2864
|
+
if (typeof result === "string") return result;
|
|
2865
|
+
try {
|
|
2866
|
+
return JSON.stringify(result, null, 2);
|
|
2867
|
+
} catch {
|
|
2868
|
+
return String(result);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
var ToolFallback = memo3(
|
|
2872
|
+
ToolFallbackImpl
|
|
2873
|
+
);
|
|
2874
|
+
ToolFallback.displayName = "ToolFallback";
|
|
2875
|
+
|
|
2876
|
+
// src/artifacts/tool-artifact.tsx
|
|
2877
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
2878
|
+
var ToolArtifactFallback = (props) => {
|
|
2879
|
+
const registry = useArtifactRegistry();
|
|
2880
|
+
const isRunning = props.status?.type === "running";
|
|
2881
|
+
if (!isRunning) {
|
|
2882
|
+
const artifact = parseArtifactFromToolResult(props.result);
|
|
2883
|
+
if (artifact) {
|
|
2884
|
+
const Renderer = registry[artifact.type];
|
|
2885
|
+
if (Renderer) {
|
|
2886
|
+
return /* @__PURE__ */ jsx22(Renderer, { artifact });
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
return /* @__PURE__ */ jsx22(ToolFallback, { ...props });
|
|
2891
|
+
};
|
|
2892
|
+
|
|
2893
|
+
// src/components/composer.tsx
|
|
2894
|
+
import {
|
|
2895
|
+
AuiIf,
|
|
2896
|
+
ComposerPrimitive as ComposerPrimitive2,
|
|
2897
|
+
useComposerRuntime
|
|
2898
|
+
} from "@assistant-ui/react";
|
|
2899
|
+
import { ArrowUpIcon, SquareIcon } from "lucide-react";
|
|
2900
|
+
import { Fragment as Fragment2, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2901
|
+
var Composer = ({
|
|
2902
|
+
placeholder = "Send a message...",
|
|
2903
|
+
showAttachments = true,
|
|
2904
|
+
toolbar,
|
|
2905
|
+
sendTooltip = "Send message",
|
|
2906
|
+
noAutoFocus,
|
|
2907
|
+
className
|
|
2908
|
+
}) => {
|
|
2909
|
+
return /* @__PURE__ */ jsx23(
|
|
2910
|
+
ComposerPrimitive2.Root,
|
|
2911
|
+
{
|
|
2912
|
+
className: cn(
|
|
2913
|
+
"aui-composer-root relative mt-3 flex w-full flex-col",
|
|
2914
|
+
className
|
|
2915
|
+
),
|
|
2916
|
+
children: /* @__PURE__ */ jsxs12(
|
|
2917
|
+
ComposerPrimitive2.AttachmentDropzone,
|
|
2918
|
+
{
|
|
2919
|
+
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",
|
|
2920
|
+
children: [
|
|
2921
|
+
showAttachments && /* @__PURE__ */ jsx23(ComposerAttachments, {}),
|
|
2922
|
+
/* @__PURE__ */ jsx23(ComposerInput, { placeholder, autoFocus: !noAutoFocus }),
|
|
2923
|
+
/* @__PURE__ */ jsx23(
|
|
2924
|
+
ComposerToolbar,
|
|
2925
|
+
{
|
|
2926
|
+
showAttachments,
|
|
2927
|
+
toolbar,
|
|
2928
|
+
sendTooltip
|
|
2929
|
+
}
|
|
2930
|
+
)
|
|
2931
|
+
]
|
|
2932
|
+
}
|
|
2933
|
+
)
|
|
2934
|
+
}
|
|
2935
|
+
);
|
|
2936
|
+
};
|
|
2937
|
+
var ComposerInput = ({
|
|
2938
|
+
placeholder,
|
|
2939
|
+
autoFocus
|
|
2940
|
+
}) => {
|
|
2941
|
+
const composer = useComposerRuntime();
|
|
2942
|
+
const onKeyDown = (e) => {
|
|
2943
|
+
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
|
|
2944
|
+
e.preventDefault();
|
|
2945
|
+
composer.send();
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
const onInput = (e) => {
|
|
2949
|
+
const el = e.currentTarget;
|
|
2950
|
+
el.style.height = "auto";
|
|
2951
|
+
el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
|
|
2952
|
+
};
|
|
2953
|
+
return /* @__PURE__ */ jsx23(
|
|
2954
|
+
ComposerPrimitive2.Input,
|
|
2955
|
+
{
|
|
2956
|
+
placeholder,
|
|
2957
|
+
className: "aui-composer-input mb-1 max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-2 pb-3 text-sm outline-none placeholder:text-muted-foreground focus-visible:ring-0",
|
|
2958
|
+
rows: 1,
|
|
2959
|
+
autoFocus,
|
|
2960
|
+
"aria-label": "Message input",
|
|
2961
|
+
onKeyDown,
|
|
2962
|
+
onInput
|
|
2963
|
+
}
|
|
2964
|
+
);
|
|
2965
|
+
};
|
|
2966
|
+
var ComposerToolbar = ({ showAttachments, toolbar, sendTooltip }) => {
|
|
2967
|
+
return /* @__PURE__ */ jsxs12("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center gap-1", children: [
|
|
2968
|
+
showAttachments && /* @__PURE__ */ jsx23(ComposerAddAttachment, {}),
|
|
2969
|
+
toolbar,
|
|
2970
|
+
/* @__PURE__ */ jsx23("div", { className: "flex-1" }),
|
|
2971
|
+
/* @__PURE__ */ jsx23(ComposerSendOrCancel, { sendTooltip })
|
|
2972
|
+
] });
|
|
2973
|
+
};
|
|
2974
|
+
var ComposerSendOrCancel = ({ sendTooltip }) => {
|
|
2975
|
+
return /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
2976
|
+
/* @__PURE__ */ jsx23(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx23(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx23(
|
|
2977
|
+
TooltipIconButton,
|
|
1132
2978
|
{
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
2979
|
+
tooltip: sendTooltip,
|
|
2980
|
+
side: "bottom",
|
|
2981
|
+
type: "submit",
|
|
2982
|
+
variant: "default",
|
|
2983
|
+
size: "icon",
|
|
2984
|
+
className: "aui-composer-send size-8 rounded-full",
|
|
2985
|
+
"aria-label": "Send message",
|
|
2986
|
+
children: /* @__PURE__ */ jsx23(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
|
|
1138
2987
|
}
|
|
1139
|
-
)
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
2988
|
+
) }) }),
|
|
2989
|
+
/* @__PURE__ */ jsx23(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx23(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx23(
|
|
2990
|
+
Button,
|
|
2991
|
+
{
|
|
2992
|
+
type: "button",
|
|
2993
|
+
variant: "default",
|
|
2994
|
+
size: "icon",
|
|
2995
|
+
className: "aui-composer-cancel size-8 rounded-full",
|
|
2996
|
+
"aria-label": "Stop generating",
|
|
2997
|
+
children: /* @__PURE__ */ jsx23(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
|
|
2998
|
+
}
|
|
2999
|
+
) }) })
|
|
3000
|
+
] });
|
|
3001
|
+
};
|
|
1149
3002
|
|
|
1150
|
-
// src/
|
|
1151
|
-
import { motion } from "motion/react";
|
|
3003
|
+
// src/components/suggestions.tsx
|
|
1152
3004
|
import {
|
|
1153
|
-
|
|
1154
|
-
useMemo
|
|
3005
|
+
useEffect as useEffect4,
|
|
3006
|
+
useMemo as useMemo5,
|
|
3007
|
+
useState as useState7
|
|
1155
3008
|
} from "react";
|
|
1156
|
-
import {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
spread = 2
|
|
3009
|
+
import { useThreadRuntime as useThreadRuntime3 } from "@assistant-ui/react";
|
|
3010
|
+
import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3011
|
+
var Suggestions = ({
|
|
3012
|
+
suggestions,
|
|
3013
|
+
layout = "grid",
|
|
3014
|
+
className
|
|
1163
3015
|
}) => {
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
() => (children?.length ?? 0) * spread,
|
|
1169
|
-
[children, spread]
|
|
1170
|
-
);
|
|
1171
|
-
return /* @__PURE__ */ jsx10(
|
|
1172
|
-
MotionComponent,
|
|
3016
|
+
const items = useResolvedSuggestions(suggestions);
|
|
3017
|
+
if (!items || items.length === 0) return null;
|
|
3018
|
+
return /* @__PURE__ */ jsx24(
|
|
3019
|
+
"div",
|
|
1173
3020
|
{
|
|
1174
|
-
animate: { backgroundPosition: "0% center" },
|
|
1175
3021
|
className: cn(
|
|
1176
|
-
"
|
|
1177
|
-
"
|
|
3022
|
+
"aui-thread-suggestions w-full pb-4",
|
|
3023
|
+
layout === "grid" ? "grid gap-2 @md:grid-cols-2" : "flex gap-2 overflow-x-auto pb-1 [&::-webkit-scrollbar]:hidden",
|
|
1178
3024
|
className
|
|
1179
3025
|
),
|
|
1180
|
-
|
|
1181
|
-
style: {
|
|
1182
|
-
"--spread": `${dynamicSpread}px`,
|
|
1183
|
-
backgroundImage: "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))"
|
|
1184
|
-
},
|
|
1185
|
-
transition: {
|
|
1186
|
-
repeat: Number.POSITIVE_INFINITY,
|
|
1187
|
-
duration,
|
|
1188
|
-
ease: "linear"
|
|
1189
|
-
},
|
|
1190
|
-
children
|
|
3026
|
+
children: items.map((s, i) => /* @__PURE__ */ jsx24(SuggestionChip, { suggestion: s, compact: layout === "row" }, s.title + i))
|
|
1191
3027
|
}
|
|
1192
3028
|
);
|
|
1193
3029
|
};
|
|
1194
|
-
var
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1198
|
-
var ToolFallbackImpl = ({
|
|
1199
|
-
toolName,
|
|
1200
|
-
status
|
|
3030
|
+
var SuggestionChip = ({
|
|
3031
|
+
suggestion,
|
|
3032
|
+
compact
|
|
1201
3033
|
}) => {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
3034
|
+
const runtime = useThreadRuntime3();
|
|
3035
|
+
const onClick = () => {
|
|
3036
|
+
const text = suggestion.prompt ?? suggestion.title;
|
|
3037
|
+
runtime.append({ role: "user", content: [{ type: "text", text }] });
|
|
3038
|
+
};
|
|
3039
|
+
return /* @__PURE__ */ jsx24("div", { className: "aui-thread-suggestion-display fade-in slide-in-from-bottom-2 animate-in fill-mode-both duration-200", children: /* @__PURE__ */ jsxs13(
|
|
3040
|
+
Button,
|
|
3041
|
+
{
|
|
3042
|
+
variant: "ghost",
|
|
3043
|
+
onClick,
|
|
3044
|
+
className: cn(
|
|
3045
|
+
"aui-thread-suggestion h-auto rounded-2xl border text-left text-sm transition-colors hover:bg-muted",
|
|
3046
|
+
compact ? "shrink-0 flex-row items-center gap-2 whitespace-nowrap px-3 py-2" : "w-full flex-wrap items-start justify-start gap-1 px-4 py-3 @md:flex-col"
|
|
3047
|
+
),
|
|
3048
|
+
children: [
|
|
3049
|
+
suggestion.icon && /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-icon shrink-0 text-muted-foreground", children: suggestion.icon }),
|
|
3050
|
+
/* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-text-1 font-medium", children: suggestion.title }),
|
|
3051
|
+
suggestion.description && !compact && /* @__PURE__ */ jsx24("span", { className: "aui-thread-suggestion-text-2 text-muted-foreground", children: suggestion.description })
|
|
3052
|
+
]
|
|
3053
|
+
}
|
|
3054
|
+
) });
|
|
1207
3055
|
};
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
)
|
|
1211
|
-
|
|
3056
|
+
function useResolvedSuggestions(source) {
|
|
3057
|
+
const [resolved, setResolved] = useState7(
|
|
3058
|
+
() => Array.isArray(source) ? source : void 0
|
|
3059
|
+
);
|
|
3060
|
+
useEffect4(() => {
|
|
3061
|
+
if (!source) {
|
|
3062
|
+
setResolved(void 0);
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
if (Array.isArray(source)) {
|
|
3066
|
+
setResolved(source);
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
let cancelled = false;
|
|
3070
|
+
Promise.resolve().then(() => source()).then((value) => {
|
|
3071
|
+
if (!cancelled) setResolved(value);
|
|
3072
|
+
}).catch(() => {
|
|
3073
|
+
if (!cancelled) setResolved([]);
|
|
3074
|
+
});
|
|
3075
|
+
return () => {
|
|
3076
|
+
cancelled = true;
|
|
3077
|
+
};
|
|
3078
|
+
}, [source]);
|
|
3079
|
+
return useMemo5(() => resolved, [resolved]);
|
|
3080
|
+
}
|
|
1212
3081
|
|
|
1213
3082
|
// src/components/thread.tsx
|
|
1214
3083
|
import {
|
|
1215
3084
|
ActionBarMorePrimitive,
|
|
1216
3085
|
ActionBarPrimitive,
|
|
1217
|
-
AuiIf,
|
|
1218
|
-
ComposerPrimitive as
|
|
3086
|
+
AuiIf as AuiIf2,
|
|
3087
|
+
ComposerPrimitive as ComposerPrimitive3,
|
|
1219
3088
|
ErrorPrimitive,
|
|
1220
3089
|
MessagePrimitive as MessagePrimitive2,
|
|
1221
|
-
ThreadPrimitive
|
|
1222
|
-
useThreadRuntime
|
|
3090
|
+
ThreadPrimitive
|
|
1223
3091
|
} from "@assistant-ui/react";
|
|
1224
3092
|
import {
|
|
1225
3093
|
ArrowDownIcon,
|
|
1226
|
-
|
|
1227
|
-
CheckIcon as CheckIcon2,
|
|
3094
|
+
CheckIcon as CheckIcon3,
|
|
1228
3095
|
CopyIcon as CopyIcon2,
|
|
1229
3096
|
DownloadIcon,
|
|
1230
3097
|
MoreHorizontalIcon,
|
|
1231
3098
|
PencilIcon,
|
|
1232
|
-
RefreshCwIcon
|
|
1233
|
-
SquareIcon
|
|
3099
|
+
RefreshCwIcon
|
|
1234
3100
|
} from "lucide-react";
|
|
1235
|
-
import { jsx as
|
|
3101
|
+
import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1236
3102
|
var Thread = ({
|
|
1237
3103
|
className,
|
|
1238
3104
|
maxWidth = "44rem",
|
|
1239
3105
|
welcome,
|
|
1240
3106
|
suggestions,
|
|
1241
3107
|
composerPlaceholder = "Send a message...",
|
|
1242
|
-
components
|
|
3108
|
+
components,
|
|
3109
|
+
artifacts,
|
|
3110
|
+
onArtifactEvent
|
|
1243
3111
|
}) => {
|
|
1244
3112
|
const WelcomeSlot = components?.Welcome ?? ThreadWelcome;
|
|
1245
3113
|
const ComposerSlot = components?.Composer ?? Composer;
|
|
@@ -1247,59 +3115,79 @@ var Thread = ({
|
|
|
1247
3115
|
const AssistantMessageSlot = components?.AssistantMessage ?? AssistantMessage;
|
|
1248
3116
|
const EditComposerSlot = components?.EditComposer ?? EditComposer;
|
|
1249
3117
|
const ScrollToBottomSlot = components?.ScrollToBottom ?? ThreadScrollToBottom;
|
|
1250
|
-
|
|
1251
|
-
|
|
3118
|
+
const SuggestionsSlot = components?.Suggestions ?? Suggestions;
|
|
3119
|
+
return /* @__PURE__ */ jsx25(
|
|
3120
|
+
ArtifactRegistryProvider,
|
|
1252
3121
|
{
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
),
|
|
1257
|
-
|
|
1258
|
-
children: /* @__PURE__ */ jsxs7(
|
|
1259
|
-
ThreadPrimitive.Viewport,
|
|
3122
|
+
renderers: artifacts?.renderers,
|
|
3123
|
+
override: artifacts?.override,
|
|
3124
|
+
children: /* @__PURE__ */ jsx25(UiEventProvider, { onEvent: onArtifactEvent ?? (() => {
|
|
3125
|
+
}), children: /* @__PURE__ */ jsx25(
|
|
3126
|
+
ThreadPrimitive.Root,
|
|
1260
3127
|
{
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
3128
|
+
className: cn(
|
|
3129
|
+
"aui-root aui-thread-root @container flex h-full flex-col bg-background",
|
|
3130
|
+
className
|
|
3131
|
+
),
|
|
3132
|
+
style: { ["--thread-max-width"]: maxWidth },
|
|
3133
|
+
children: /* @__PURE__ */ jsxs14(
|
|
3134
|
+
ThreadPrimitive.Viewport,
|
|
3135
|
+
{
|
|
3136
|
+
turnAnchor: "bottom",
|
|
3137
|
+
className: "aui-thread-viewport relative flex flex-1 flex-col overflow-x-auto overflow-y-scroll px-4 pt-4",
|
|
3138
|
+
children: [
|
|
3139
|
+
/* @__PURE__ */ jsx25(
|
|
3140
|
+
WelcomeSlot,
|
|
3141
|
+
{
|
|
3142
|
+
config: welcome,
|
|
3143
|
+
suggestions,
|
|
3144
|
+
Suggestions: SuggestionsSlot
|
|
3145
|
+
}
|
|
3146
|
+
),
|
|
3147
|
+
/* @__PURE__ */ jsx25(
|
|
3148
|
+
ThreadPrimitive.Messages,
|
|
3149
|
+
{
|
|
3150
|
+
components: {
|
|
3151
|
+
UserMessage: UserMessageSlot,
|
|
3152
|
+
EditComposer: EditComposerSlot,
|
|
3153
|
+
AssistantMessage: AssistantMessageSlot
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
),
|
|
3157
|
+
/* @__PURE__ */ jsxs14(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: [
|
|
3158
|
+
/* @__PURE__ */ jsx25(ScrollToBottomSlot, {}),
|
|
3159
|
+
/* @__PURE__ */ jsx25(ComposerSlot, { placeholder: composerPlaceholder })
|
|
3160
|
+
] })
|
|
3161
|
+
]
|
|
3162
|
+
}
|
|
3163
|
+
)
|
|
1280
3164
|
}
|
|
1281
|
-
)
|
|
3165
|
+
) })
|
|
1282
3166
|
}
|
|
1283
3167
|
);
|
|
1284
3168
|
};
|
|
1285
3169
|
var ThreadScrollToBottom = () => {
|
|
1286
|
-
return /* @__PURE__ */
|
|
3170
|
+
return /* @__PURE__ */ jsx25(ThreadPrimitive.ScrollToBottom, { asChild: true, children: /* @__PURE__ */ jsx25(
|
|
1287
3171
|
TooltipIconButton,
|
|
1288
3172
|
{
|
|
1289
3173
|
tooltip: "Scroll to bottom",
|
|
1290
3174
|
variant: "outline",
|
|
1291
3175
|
className: "aui-thread-scroll-to-bottom absolute -top-12 z-10 self-center rounded-full p-4 disabled:invisible dark:bg-background dark:hover:bg-accent",
|
|
1292
|
-
children: /* @__PURE__ */
|
|
3176
|
+
children: /* @__PURE__ */ jsx25(ArrowDownIcon, {})
|
|
1293
3177
|
}
|
|
1294
3178
|
) });
|
|
1295
3179
|
};
|
|
1296
|
-
var ThreadWelcome = ({
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
3180
|
+
var ThreadWelcome = ({
|
|
3181
|
+
config,
|
|
3182
|
+
suggestions,
|
|
3183
|
+
Suggestions: SuggestionsSlot = Suggestions
|
|
3184
|
+
}) => {
|
|
3185
|
+
return /* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => s.thread.isEmpty, children: /* @__PURE__ */ jsxs14("div", { className: "aui-thread-welcome-root mx-auto my-auto flex w-full max-w-(--thread-max-width) grow flex-col", children: [
|
|
3186
|
+
/* @__PURE__ */ jsx25("div", { className: "aui-thread-welcome-center flex w-full grow flex-col items-center justify-center", children: /* @__PURE__ */ jsxs14("div", { className: "aui-thread-welcome-message flex size-full flex-col items-center justify-center px-4 text-center", children: [
|
|
3187
|
+
/* @__PURE__ */ jsxs14("div", { className: "fade-in animate-in fill-mode-both relative mb-6 flex size-14 items-center justify-center duration-300", children: [
|
|
3188
|
+
/* @__PURE__ */ jsx25("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" }),
|
|
3189
|
+
/* @__PURE__ */ jsx25("div", { className: "animate-ai-pulse-ring absolute inset-0" }),
|
|
3190
|
+
/* @__PURE__ */ jsx25(
|
|
1303
3191
|
"svg",
|
|
1304
3192
|
{
|
|
1305
3193
|
xmlns: "http://www.w3.org/2000/svg",
|
|
@@ -1310,110 +3198,45 @@ var ThreadWelcome = ({ config, suggestions }) => {
|
|
|
1310
3198
|
strokeLinecap: "round",
|
|
1311
3199
|
strokeLinejoin: "round",
|
|
1312
3200
|
className: "animate-ai-breathe relative size-7 text-primary/75",
|
|
1313
|
-
children: /* @__PURE__ */
|
|
3201
|
+
children: /* @__PURE__ */ jsx25("path", { d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" })
|
|
1314
3202
|
}
|
|
1315
3203
|
)
|
|
1316
3204
|
] }),
|
|
1317
|
-
/* @__PURE__ */
|
|
1318
|
-
/* @__PURE__ */
|
|
3205
|
+
/* @__PURE__ */ jsx25("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?" }),
|
|
3206
|
+
/* @__PURE__ */ jsx25("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." })
|
|
1319
3207
|
] }) }),
|
|
1320
|
-
suggestions &&
|
|
1321
|
-
] }) });
|
|
1322
|
-
};
|
|
1323
|
-
var ThreadSuggestions = ({ suggestions }) => {
|
|
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)) });
|
|
1325
|
-
};
|
|
1326
|
-
var ThreadSuggestionItem = ({ title, description }) => {
|
|
1327
|
-
const runtime = useThreadRuntime();
|
|
1328
|
-
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(
|
|
1329
|
-
Button,
|
|
1330
|
-
{
|
|
1331
|
-
variant: "ghost",
|
|
1332
|
-
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",
|
|
1333
|
-
onClick: () => runtime.append({
|
|
1334
|
-
role: "user",
|
|
1335
|
-
content: [{ type: "text", text: title }]
|
|
1336
|
-
}),
|
|
1337
|
-
children: [
|
|
1338
|
-
/* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-1 font-medium", children: title }),
|
|
1339
|
-
description && /* @__PURE__ */ jsx12("span", { className: "aui-thread-welcome-suggestion-text-2 text-muted-foreground", children: description })
|
|
1340
|
-
]
|
|
1341
|
-
}
|
|
1342
|
-
) });
|
|
1343
|
-
};
|
|
1344
|
-
var Composer = ({ placeholder }) => {
|
|
1345
|
-
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: [
|
|
1346
|
-
/* @__PURE__ */ jsx12(ComposerAttachments, {}),
|
|
1347
|
-
/* @__PURE__ */ jsx12(
|
|
1348
|
-
ComposerPrimitive2.Input,
|
|
1349
|
-
{
|
|
1350
|
-
placeholder: placeholder ?? "Send a message...",
|
|
1351
|
-
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",
|
|
1352
|
-
rows: 1,
|
|
1353
|
-
autoFocus: true,
|
|
1354
|
-
"aria-label": "Message input"
|
|
1355
|
-
}
|
|
1356
|
-
),
|
|
1357
|
-
/* @__PURE__ */ jsx12(ComposerAction, {})
|
|
3208
|
+
suggestions && /* @__PURE__ */ jsx25(SuggestionsSlot, { suggestions })
|
|
1358
3209
|
] }) });
|
|
1359
3210
|
};
|
|
1360
|
-
var ComposerAction = () => {
|
|
1361
|
-
return /* @__PURE__ */ jsxs7("div", { className: "aui-composer-action-wrapper relative mx-2 mb-2 flex items-center justify-end", children: [
|
|
1362
|
-
/* @__PURE__ */ jsx12(AuiIf, { condition: (s) => !s.thread.isRunning, children: /* @__PURE__ */ jsx12(ComposerPrimitive2.Send, { asChild: true, children: /* @__PURE__ */ jsx12(
|
|
1363
|
-
TooltipIconButton,
|
|
1364
|
-
{
|
|
1365
|
-
tooltip: "Send message",
|
|
1366
|
-
side: "bottom",
|
|
1367
|
-
type: "submit",
|
|
1368
|
-
variant: "default",
|
|
1369
|
-
size: "icon",
|
|
1370
|
-
className: "aui-composer-send size-8 rounded-full",
|
|
1371
|
-
"aria-label": "Send message",
|
|
1372
|
-
children: /* @__PURE__ */ jsx12(ArrowUpIcon, { className: "aui-composer-send-icon size-4" })
|
|
1373
|
-
}
|
|
1374
|
-
) }) }),
|
|
1375
|
-
/* @__PURE__ */ jsx12(AuiIf, { condition: (s) => s.thread.isRunning, children: /* @__PURE__ */ jsx12(ComposerPrimitive2.Cancel, { asChild: true, children: /* @__PURE__ */ jsx12(
|
|
1376
|
-
Button,
|
|
1377
|
-
{
|
|
1378
|
-
type: "button",
|
|
1379
|
-
variant: "default",
|
|
1380
|
-
size: "icon",
|
|
1381
|
-
className: "aui-composer-cancel size-8 rounded-full",
|
|
1382
|
-
"aria-label": "Stop generating",
|
|
1383
|
-
children: /* @__PURE__ */ jsx12(SquareIcon, { className: "aui-composer-cancel-icon size-3 fill-current" })
|
|
1384
|
-
}
|
|
1385
|
-
) }) })
|
|
1386
|
-
] });
|
|
1387
|
-
};
|
|
1388
3211
|
var MessageError = () => {
|
|
1389
|
-
return /* @__PURE__ */
|
|
3212
|
+
return /* @__PURE__ */ jsx25(MessagePrimitive2.Error, { children: /* @__PURE__ */ jsx25(ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200", children: /* @__PURE__ */ jsx25(ErrorPrimitive.Message, { className: "aui-message-error-message line-clamp-2" }) }) });
|
|
1390
3213
|
};
|
|
1391
3214
|
var AssistantMessage = () => {
|
|
1392
|
-
return /* @__PURE__ */
|
|
3215
|
+
return /* @__PURE__ */ jsxs14(
|
|
1393
3216
|
MessagePrimitive2.Root,
|
|
1394
3217
|
{
|
|
1395
3218
|
className: "aui-assistant-message-root fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150",
|
|
1396
3219
|
"data-role": "assistant",
|
|
1397
3220
|
children: [
|
|
1398
|
-
/* @__PURE__ */
|
|
1399
|
-
/* @__PURE__ */
|
|
3221
|
+
/* @__PURE__ */ jsxs14("div", { className: "aui-assistant-message-content wrap-break-word px-2 text-foreground leading-relaxed", children: [
|
|
3222
|
+
/* @__PURE__ */ jsx25(
|
|
1400
3223
|
MessagePrimitive2.Parts,
|
|
1401
3224
|
{
|
|
1402
3225
|
components: {
|
|
1403
3226
|
Text: MarkdownText,
|
|
1404
|
-
tools: { Fallback:
|
|
3227
|
+
tools: { Fallback: ToolArtifactFallback }
|
|
1405
3228
|
}
|
|
1406
3229
|
}
|
|
1407
3230
|
),
|
|
1408
|
-
/* @__PURE__ */
|
|
3231
|
+
/* @__PURE__ */ jsx25(MessageError, {})
|
|
1409
3232
|
] }),
|
|
1410
|
-
/* @__PURE__ */
|
|
3233
|
+
/* @__PURE__ */ jsx25("div", { className: "aui-assistant-message-footer mt-1 ml-2 flex", children: /* @__PURE__ */ jsx25(AssistantActionBar, {}) })
|
|
1411
3234
|
]
|
|
1412
3235
|
}
|
|
1413
3236
|
);
|
|
1414
3237
|
};
|
|
1415
3238
|
var AssistantActionBar = () => {
|
|
1416
|
-
return /* @__PURE__ */
|
|
3239
|
+
return /* @__PURE__ */ jsxs14(
|
|
1417
3240
|
ActionBarPrimitive.Root,
|
|
1418
3241
|
{
|
|
1419
3242
|
hideWhenRunning: true,
|
|
@@ -1421,28 +3244,28 @@ var AssistantActionBar = () => {
|
|
|
1421
3244
|
autohideFloat: "single-branch",
|
|
1422
3245
|
className: "aui-assistant-action-bar-root col-start-3 row-start-2 -ml-1 flex gap-1 text-muted-foreground data-floating:absolute data-floating:rounded-md data-floating:border data-floating:bg-background data-floating:p-1 data-floating:shadow-sm",
|
|
1423
3246
|
children: [
|
|
1424
|
-
/* @__PURE__ */
|
|
1425
|
-
/* @__PURE__ */
|
|
1426
|
-
/* @__PURE__ */
|
|
3247
|
+
/* @__PURE__ */ jsx25(ActionBarPrimitive.Copy, { asChild: true, children: /* @__PURE__ */ jsxs14(TooltipIconButton, { tooltip: "Copy", children: [
|
|
3248
|
+
/* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => s.message.isCopied, children: /* @__PURE__ */ jsx25(CheckIcon3, {}) }),
|
|
3249
|
+
/* @__PURE__ */ jsx25(AuiIf2, { condition: (s) => !s.message.isCopied, children: /* @__PURE__ */ jsx25(CopyIcon2, {}) })
|
|
1427
3250
|
] }) }),
|
|
1428
|
-
/* @__PURE__ */
|
|
1429
|
-
/* @__PURE__ */
|
|
1430
|
-
/* @__PURE__ */
|
|
3251
|
+
/* @__PURE__ */ jsx25(ActionBarPrimitive.Reload, { asChild: true, children: /* @__PURE__ */ jsx25(TooltipIconButton, { tooltip: "Refresh", children: /* @__PURE__ */ jsx25(RefreshCwIcon, {}) }) }),
|
|
3252
|
+
/* @__PURE__ */ jsxs14(ActionBarMorePrimitive.Root, { children: [
|
|
3253
|
+
/* @__PURE__ */ jsx25(ActionBarMorePrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx25(
|
|
1431
3254
|
TooltipIconButton,
|
|
1432
3255
|
{
|
|
1433
3256
|
tooltip: "More",
|
|
1434
3257
|
className: "data-[state=open]:bg-accent",
|
|
1435
|
-
children: /* @__PURE__ */
|
|
3258
|
+
children: /* @__PURE__ */ jsx25(MoreHorizontalIcon, {})
|
|
1436
3259
|
}
|
|
1437
3260
|
) }),
|
|
1438
|
-
/* @__PURE__ */
|
|
3261
|
+
/* @__PURE__ */ jsx25(
|
|
1439
3262
|
ActionBarMorePrimitive.Content,
|
|
1440
3263
|
{
|
|
1441
3264
|
side: "bottom",
|
|
1442
3265
|
align: "start",
|
|
1443
3266
|
className: "aui-action-bar-more-content z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
|
1444
|
-
children: /* @__PURE__ */
|
|
1445
|
-
/* @__PURE__ */
|
|
3267
|
+
children: /* @__PURE__ */ jsx25(ActionBarPrimitive.ExportMarkdown, { asChild: true, children: /* @__PURE__ */ jsxs14(ActionBarMorePrimitive.Item, { className: "aui-action-bar-more-item flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground", children: [
|
|
3268
|
+
/* @__PURE__ */ jsx25(DownloadIcon, { className: "size-4" }),
|
|
1446
3269
|
"Export as Markdown"
|
|
1447
3270
|
] }) })
|
|
1448
3271
|
}
|
|
@@ -1453,80 +3276,388 @@ var AssistantActionBar = () => {
|
|
|
1453
3276
|
);
|
|
1454
3277
|
};
|
|
1455
3278
|
var UserMessage = () => {
|
|
1456
|
-
return /* @__PURE__ */
|
|
3279
|
+
return /* @__PURE__ */ jsxs14(
|
|
1457
3280
|
MessagePrimitive2.Root,
|
|
1458
3281
|
{
|
|
1459
3282
|
className: "aui-user-message-root fade-in slide-in-from-bottom-1 mx-auto grid w-full max-w-(--thread-max-width) animate-in auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] content-start gap-y-2 px-2 py-3 duration-150 [&:where(>*)]:col-start-2",
|
|
1460
3283
|
"data-role": "user",
|
|
1461
3284
|
children: [
|
|
1462
|
-
/* @__PURE__ */
|
|
1463
|
-
/* @__PURE__ */
|
|
1464
|
-
/* @__PURE__ */
|
|
1465
|
-
/* @__PURE__ */
|
|
3285
|
+
/* @__PURE__ */ jsx25(UserMessageAttachments, {}),
|
|
3286
|
+
/* @__PURE__ */ jsxs14("div", { className: "aui-user-message-content-wrapper relative col-start-2 min-w-0", children: [
|
|
3287
|
+
/* @__PURE__ */ jsx25("div", { className: "aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground", children: /* @__PURE__ */ jsx25(MessagePrimitive2.Parts, {}) }),
|
|
3288
|
+
/* @__PURE__ */ jsx25("div", { className: "aui-user-action-bar-wrapper absolute top-1/2 left-0 -translate-x-full -translate-y-1/2 pr-2", children: /* @__PURE__ */ jsx25(UserActionBar, {}) })
|
|
1466
3289
|
] })
|
|
1467
3290
|
]
|
|
1468
3291
|
}
|
|
1469
3292
|
);
|
|
1470
3293
|
};
|
|
1471
3294
|
var UserActionBar = () => {
|
|
1472
|
-
return /* @__PURE__ */
|
|
3295
|
+
return /* @__PURE__ */ jsx25(
|
|
1473
3296
|
ActionBarPrimitive.Root,
|
|
1474
3297
|
{
|
|
1475
3298
|
hideWhenRunning: true,
|
|
1476
3299
|
autohide: "not-last",
|
|
1477
3300
|
className: "aui-user-action-bar-root flex flex-col items-end",
|
|
1478
|
-
children: /* @__PURE__ */
|
|
3301
|
+
children: /* @__PURE__ */ jsx25(ActionBarPrimitive.Edit, { asChild: true, children: /* @__PURE__ */ jsx25(TooltipIconButton, { tooltip: "Edit", className: "aui-user-action-edit p-4", children: /* @__PURE__ */ jsx25(PencilIcon, {}) }) })
|
|
1479
3302
|
}
|
|
1480
3303
|
);
|
|
1481
3304
|
};
|
|
1482
3305
|
var EditComposer = () => {
|
|
1483
|
-
return /* @__PURE__ */
|
|
1484
|
-
/* @__PURE__ */
|
|
1485
|
-
|
|
3306
|
+
return /* @__PURE__ */ jsx25(MessagePrimitive2.Root, { className: "aui-edit-composer-wrapper mx-auto flex w-full max-w-(--thread-max-width) flex-col px-2 py-3", children: /* @__PURE__ */ jsxs14(ComposerPrimitive3.Root, { className: "aui-edit-composer-root ml-auto flex w-full max-w-[85%] flex-col rounded-2xl bg-muted", children: [
|
|
3307
|
+
/* @__PURE__ */ jsx25(
|
|
3308
|
+
ComposerPrimitive3.Input,
|
|
1486
3309
|
{
|
|
1487
3310
|
className: "aui-edit-composer-input min-h-14 w-full resize-none bg-transparent p-4 text-foreground text-sm outline-none",
|
|
1488
3311
|
autoFocus: true
|
|
1489
3312
|
}
|
|
1490
3313
|
),
|
|
1491
|
-
/* @__PURE__ */
|
|
1492
|
-
/* @__PURE__ */
|
|
1493
|
-
/* @__PURE__ */
|
|
3314
|
+
/* @__PURE__ */ jsxs14("div", { className: "aui-edit-composer-footer mx-3 mb-3 flex items-center gap-2 self-end", children: [
|
|
3315
|
+
/* @__PURE__ */ jsx25(ComposerPrimitive3.Cancel, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { variant: "ghost", size: "sm", children: "Cancel" }) }),
|
|
3316
|
+
/* @__PURE__ */ jsx25(ComposerPrimitive3.Send, { asChild: true, children: /* @__PURE__ */ jsx25(Button, { size: "sm", children: "Update" }) })
|
|
1494
3317
|
] })
|
|
1495
3318
|
] }) });
|
|
1496
3319
|
};
|
|
1497
3320
|
|
|
1498
3321
|
// src/components/chat.tsx
|
|
1499
|
-
import { jsx as
|
|
3322
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
1500
3323
|
function TimbalChat({
|
|
1501
3324
|
workforceId,
|
|
1502
3325
|
baseUrl,
|
|
1503
3326
|
fetch: fetch2,
|
|
3327
|
+
attachments,
|
|
3328
|
+
attachmentsUploadUrl,
|
|
3329
|
+
attachmentsAccept,
|
|
3330
|
+
debug,
|
|
1504
3331
|
...threadProps
|
|
1505
3332
|
}) {
|
|
1506
|
-
return /* @__PURE__ */
|
|
3333
|
+
return /* @__PURE__ */ jsx26(
|
|
3334
|
+
TimbalRuntimeProvider,
|
|
3335
|
+
{
|
|
3336
|
+
workforceId,
|
|
3337
|
+
baseUrl,
|
|
3338
|
+
fetch: fetch2,
|
|
3339
|
+
attachments,
|
|
3340
|
+
attachmentsUploadUrl,
|
|
3341
|
+
attachmentsAccept,
|
|
3342
|
+
debug,
|
|
3343
|
+
children: /* @__PURE__ */ jsx26(Thread, { ...threadProps })
|
|
3344
|
+
}
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// src/artifacts/agent-instructions.ts
|
|
3349
|
+
var ARTIFACT_AGENT_INSTRUCTIONS = `
|
|
3350
|
+
## Rich artifacts (Timbal chat UI)
|
|
3351
|
+
|
|
3352
|
+
When you need charts, tables, choice widgets, or interactive controls, return a **JSON artifact object** instead of plain prose. The chat UI renders these automatically.
|
|
3353
|
+
|
|
3354
|
+
### Delivery channels (either works)
|
|
3355
|
+
|
|
3356
|
+
1. **Tool result (preferred)** \u2014 return a single JSON object (or a JSON string) from a tool. The object must include a string \`type\` field.
|
|
3357
|
+
2. **Inline markdown fence** \u2014 embed the same JSON inside a fenced block:
|
|
3358
|
+
|
|
3359
|
+
\`\`\`timbal-artifact
|
|
3360
|
+
{"type":"chart","data":[{"month":"Jan","sales":120}]}
|
|
3361
|
+
\`\`\`
|
|
3362
|
+
|
|
3363
|
+
The alias \`\`\`timbal\`\`\` is also accepted.
|
|
3364
|
+
|
|
3365
|
+
### Built-in artifact types
|
|
3366
|
+
|
|
3367
|
+
| \`type\` | Use for |
|
|
3368
|
+
|---|---|
|
|
3369
|
+
| \`chart\` | Bar, line, area, or pie charts. Fields: \`data\`, optional \`chartType\`, \`xKey\`, \`dataKey\`, \`title\`, \`unit\`. |
|
|
3370
|
+
| \`table\` | Tabular data. Fields: \`rows\`, optional \`columns\`, \`title\`. |
|
|
3371
|
+
| \`question\` | In-thread multiple choice. Fields: \`options: [{ id, label, description? }]\`, optional \`prompt\`, \`multi\`. User replies are sent back as a normal user message. |
|
|
3372
|
+
| \`html\` | Custom HTML/CSS/JS in an iframe. Fields: \`content\` (HTML document or fragment), optional \`title\`, \`height\`, \`sandboxed\` (default \`true\`; set \`false\` for unrestricted scripts/CDN). |
|
|
3373
|
+
| \`json\` | Fallback structured view. Fields: \`data\`, optional \`title\`. |
|
|
3374
|
+
| \`ui\` | **Interactive UI** composed from a fixed node palette (hover, click, drag). See below. |
|
|
3375
|
+
|
|
3376
|
+
### When to use \`type: "html"\`
|
|
3377
|
+
|
|
3378
|
+
Use \`html\` when the user wants a **rich visual or interactive page** that does not fit the \`ui\` palette \u2014 e.g. styled layouts, SVG graphics, CSS animations, canvas, small games, calculators, or multi-section mockups. Pass a full HTML document or a body fragment in \`content\`.
|
|
3379
|
+
|
|
3380
|
+
- Inline \`<style>\`, \`<script>\`, SVG, and canvas are supported.
|
|
3381
|
+
- Default \`sandboxed: true\` runs in an isolated iframe with scripts enabled.
|
|
3382
|
+
- Set \`sandboxed: false\` only for trusted content that needs external CDN scripts/styles or full DOM freedom.
|
|
3383
|
+
- Prefer \`ui\` when controls should send chat messages or host events; prefer \`html\` for self-contained mini-apps and visual demos.
|
|
3384
|
+
|
|
3385
|
+
### When to use \`type: "ui"\`
|
|
3386
|
+
|
|
3387
|
+
Use a \`ui\` artifact when the user should **hover, click, drag, or adjust controls** in-thread and those actions should integrate with the chat runtime (messages, \`onArtifactEvent\`). For standalone visual/interactive HTML, use \`html\` instead.
|
|
3388
|
+
|
|
3389
|
+
Each \`ui\` artifact has:
|
|
3390
|
+
|
|
3391
|
+
- \`initialState\` \u2014 optional object seeding local state (per widget instance).
|
|
3392
|
+
- \`root\` \u2014 a single node tree (see node kinds below).
|
|
3393
|
+
- optional \`title\` \u2014 card heading.
|
|
3394
|
+
|
|
3395
|
+
**Bindings:** anywhere a primitive is accepted, you may use \`{ "$bind": "dotted.path" }\` to read from \`initialState\` / local state (e.g. \`{ "$bind": "qty" }\`).
|
|
3396
|
+
|
|
3397
|
+
**Actions:** nodes may attach \`onClick\`, \`onChange\`, or \`onDragEnd\` with one action or an array:
|
|
3398
|
+
|
|
3399
|
+
| Action | Shape | Effect |
|
|
3400
|
+
|---|---|---|
|
|
3401
|
+
| User message | \`{ "kind": "message", "text": "..." }\` or \`{ "kind": "message", "text": { "$bind": "path" } }\` | Sends text as the next user message. |
|
|
3402
|
+
| Set state | \`{ "kind": "set", "path": "foo", "value": 1 }\` | Writes local widget state. |
|
|
3403
|
+
| Toggle boolean | \`{ "kind": "toggle", "path": "enabled" }\` | Flips a boolean at \`path\`. |
|
|
3404
|
+
| Host event | \`{ "kind": "emit", "name": "event-name", "payload": { ... } }\` | Bubbles to the host app (\`onArtifactEvent\` on \`<Thread>\`). |
|
|
3405
|
+
|
|
3406
|
+
### \`ui\` node palette (\`root.kind\`)
|
|
3407
|
+
|
|
3408
|
+
| \`kind\` | Purpose | Key fields |
|
|
3409
|
+
|---|---|---|
|
|
3410
|
+
| \`box\` | Layout container | \`children\`, \`direction\` (\`row\`/\`col\`), \`gap\`, \`padding\`, \`align\`, \`justify\`, \`wrap\` |
|
|
3411
|
+
| \`text\` | Body text | \`value\`, optional \`muted\`, \`size\`, \`weight\` |
|
|
3412
|
+
| \`heading\` | Heading | \`value\`, optional \`level\` (1\u20134) |
|
|
3413
|
+
| \`badge\` | Pill label | \`value\`, optional \`tone\` (\`default\`, \`primary\`, \`success\`, \`warn\`, \`danger\`) |
|
|
3414
|
+
| \`button\` | Clickable button | \`label\`, optional \`variant\`, \`size\`, \`disabled\`, \`onClick\` |
|
|
3415
|
+
| \`toggle\` | Boolean switch | \`binding\` (state path), optional \`label\`, \`onChange\` |
|
|
3416
|
+
| \`slider\` | Numeric range | \`binding\`, optional \`min\`, \`max\`, \`step\`, \`label\`, \`showValue\`, \`onChange\` |
|
|
3417
|
+
| \`tooltip\` | Hover tooltip | \`content\`, \`child\` (single node), optional \`side\` |
|
|
3418
|
+
| \`draggable\` | Drag gesture | \`child\`, optional \`axis\` (\`x\`/\`y\`/\`both\`), \`snapBack\`, \`onDragEnd\` |
|
|
3419
|
+
| \`custom\` | Host-registered widget | \`name\`, optional \`props\`, \`children\` \u2014 only if the app registered that name |
|
|
3420
|
+
|
|
3421
|
+
### Example \`ui\` artifact
|
|
3422
|
+
|
|
3423
|
+
\`\`\`json
|
|
3424
|
+
{
|
|
3425
|
+
"type": "ui",
|
|
3426
|
+
"title": "Configure plan",
|
|
3427
|
+
"initialState": { "qty": 1, "premium": false },
|
|
3428
|
+
"root": {
|
|
3429
|
+
"kind": "box",
|
|
3430
|
+
"direction": "col",
|
|
3431
|
+
"gap": 3,
|
|
3432
|
+
"children": [
|
|
3433
|
+
{ "kind": "heading", "value": "Choose quantity", "level": 3 },
|
|
3434
|
+
{
|
|
3435
|
+
"kind": "tooltip",
|
|
3436
|
+
"content": "Drag to adjust quantity",
|
|
3437
|
+
"child": {
|
|
3438
|
+
"kind": "slider",
|
|
3439
|
+
"binding": "qty",
|
|
3440
|
+
"min": 1,
|
|
3441
|
+
"max": 50,
|
|
3442
|
+
"label": "Quantity",
|
|
3443
|
+
"onChange": { "kind": "emit", "name": "qty-changed" }
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
{ "kind": "toggle", "binding": "premium", "label": "Premium support" },
|
|
3447
|
+
{
|
|
3448
|
+
"kind": "button",
|
|
3449
|
+
"label": "Confirm",
|
|
3450
|
+
"onClick": { "kind": "message", "text": { "$bind": "qty" } }
|
|
3451
|
+
}
|
|
3452
|
+
]
|
|
3453
|
+
}
|
|
1507
3454
|
}
|
|
3455
|
+
\`\`\`
|
|
3456
|
+
|
|
3457
|
+
### Rules
|
|
3458
|
+
|
|
3459
|
+
- Always set \`type\` to a built-in value above unless the app documented a custom type.
|
|
3460
|
+
- Prefer \`ui\` over \`html\` when actions must bubble to the host chat (\`message\`, \`emit\`).
|
|
3461
|
+
- Prefer \`question\` for simple A/B/C choices; use \`ui\` when you need sliders, toggles, drag, or multi-control layouts.
|
|
3462
|
+
- Keep \`data\` arrays reasonably small (charts/tables).
|
|
3463
|
+
|
|
3464
|
+
### After calling an artifact tool (critical)
|
|
3465
|
+
|
|
3466
|
+
When you call a tool that returns an artifact (\`make_chart\`, \`ask_question\`, \`show_table\`, \`show_html\`, \`make_ui_demo\`, etc.):
|
|
3467
|
+
|
|
3468
|
+
1. **Do not** paste, quote, paraphrase as JSON, or fence the tool return value in your assistant message. The chat UI already renders it from the tool result.
|
|
3469
|
+
2. **Do not** emit a matching \`\`\`timbal-artifact\`\`\` block for the same payload \u2014 pick **one** channel (tool result only).
|
|
3470
|
+
3. Your follow-up text should be **empty**, or at most **one short sentence** (e.g. "Pick an option above." / "Try the controls."). Never include \`type\`, \`options\`, \`data\`, or dict/JSON syntax.
|
|
3471
|
+
4. Treat the widget as visible to the user; refer to it as "above" / "the chart" / "the choices" \u2014 never reproduce its contents.
|
|
3472
|
+
`.trim();
|
|
1508
3473
|
|
|
1509
3474
|
// src/index.ts
|
|
1510
3475
|
import {
|
|
1511
3476
|
ThreadPrimitive as ThreadPrimitive2,
|
|
1512
3477
|
MessagePrimitive as MessagePrimitive3,
|
|
1513
|
-
ComposerPrimitive as
|
|
3478
|
+
ComposerPrimitive as ComposerPrimitive4,
|
|
1514
3479
|
ActionBarPrimitive as ActionBarPrimitive2,
|
|
3480
|
+
AssistantRuntimeProvider as AssistantRuntimeProvider2,
|
|
1515
3481
|
useThread,
|
|
1516
|
-
useThreadRuntime as
|
|
3482
|
+
useThreadRuntime as useThreadRuntime4,
|
|
1517
3483
|
useMessageRuntime,
|
|
1518
|
-
useComposerRuntime
|
|
3484
|
+
useComposerRuntime as useComposerRuntime2
|
|
1519
3485
|
} from "@assistant-ui/react";
|
|
1520
3486
|
|
|
3487
|
+
// src/hooks/use-workforces.ts
|
|
3488
|
+
import { useEffect as useEffect5, useMemo as useMemo6, useRef as useRef2, useState as useState8 } from "react";
|
|
3489
|
+
function useWorkforces(options = {}) {
|
|
3490
|
+
const { baseUrl = "/api", fetch: fetchFn, pickInitial } = options;
|
|
3491
|
+
const [workforces, setWorkforces] = useState8([]);
|
|
3492
|
+
const [selectedId, setSelectedId] = useState8("");
|
|
3493
|
+
const [isLoading, setIsLoading] = useState8(true);
|
|
3494
|
+
const [error, setError] = useState8(null);
|
|
3495
|
+
const fetchFnRef = useRef2(fetchFn ?? authFetch);
|
|
3496
|
+
useEffect5(() => {
|
|
3497
|
+
fetchFnRef.current = fetchFn ?? authFetch;
|
|
3498
|
+
}, [fetchFn]);
|
|
3499
|
+
const pickInitialRef = useRef2(pickInitial);
|
|
3500
|
+
useEffect5(() => {
|
|
3501
|
+
pickInitialRef.current = pickInitial;
|
|
3502
|
+
}, [pickInitial]);
|
|
3503
|
+
const load = useMemo6(() => {
|
|
3504
|
+
return async () => {
|
|
3505
|
+
setIsLoading(true);
|
|
3506
|
+
setError(null);
|
|
3507
|
+
try {
|
|
3508
|
+
const res = await fetchFnRef.current(`${baseUrl}/workforce`);
|
|
3509
|
+
if (!res.ok) throw new Error(`Failed to load workforces (${res.status})`);
|
|
3510
|
+
const data = await res.json();
|
|
3511
|
+
setWorkforces(data);
|
|
3512
|
+
setSelectedId((current) => {
|
|
3513
|
+
if (current && data.some((w) => idOf(w) === current)) return current;
|
|
3514
|
+
const initial = pickInitialRef.current?.(data) ?? data.find((w) => w.type === "agent") ?? data[0];
|
|
3515
|
+
return initial ? idOf(initial) : "";
|
|
3516
|
+
});
|
|
3517
|
+
} catch (err) {
|
|
3518
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3519
|
+
} finally {
|
|
3520
|
+
setIsLoading(false);
|
|
3521
|
+
}
|
|
3522
|
+
};
|
|
3523
|
+
}, [baseUrl]);
|
|
3524
|
+
useEffect5(() => {
|
|
3525
|
+
load();
|
|
3526
|
+
}, [load]);
|
|
3527
|
+
const selected = useMemo6(
|
|
3528
|
+
() => workforces.find((w) => idOf(w) === selectedId),
|
|
3529
|
+
[workforces, selectedId]
|
|
3530
|
+
);
|
|
3531
|
+
return {
|
|
3532
|
+
workforces,
|
|
3533
|
+
selectedId,
|
|
3534
|
+
setSelectedId,
|
|
3535
|
+
selected,
|
|
3536
|
+
isLoading,
|
|
3537
|
+
error,
|
|
3538
|
+
refresh: load
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
function idOf(item) {
|
|
3542
|
+
return item.id ?? item.uid ?? item.name ?? "";
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
// src/components/workforce-selector.tsx
|
|
3546
|
+
import { ChevronDownIcon as ChevronDownIcon2 } from "lucide-react";
|
|
3547
|
+
import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3548
|
+
var WorkforceSelector = ({
|
|
3549
|
+
workforces,
|
|
3550
|
+
value,
|
|
3551
|
+
onChange,
|
|
3552
|
+
hideWhenSingle = true,
|
|
3553
|
+
className,
|
|
3554
|
+
placeholder = "Select agent"
|
|
3555
|
+
}) => {
|
|
3556
|
+
if (workforces.length === 0) return null;
|
|
3557
|
+
if (hideWhenSingle && workforces.length === 1) return null;
|
|
3558
|
+
return /* @__PURE__ */ jsxs15("div", { className: cn("aui-workforce-selector relative inline-flex items-center", className), children: [
|
|
3559
|
+
/* @__PURE__ */ jsxs15(
|
|
3560
|
+
"select",
|
|
3561
|
+
{
|
|
3562
|
+
className: "aui-workforce-selector-input h-7 cursor-pointer appearance-none rounded-md border-none bg-transparent pr-5 pl-1.5 text-xs font-medium text-muted-foreground shadow-none outline-none ring-0 transition-colors hover:text-foreground focus:ring-0",
|
|
3563
|
+
value,
|
|
3564
|
+
onChange: (e) => onChange(e.target.value),
|
|
3565
|
+
"aria-label": placeholder,
|
|
3566
|
+
children: [
|
|
3567
|
+
!value && /* @__PURE__ */ jsx27("option", { value: "", children: placeholder }),
|
|
3568
|
+
workforces.map((w) => {
|
|
3569
|
+
const id = idOf2(w);
|
|
3570
|
+
return /* @__PURE__ */ jsx27("option", { value: id, children: w.name ?? id }, id);
|
|
3571
|
+
})
|
|
3572
|
+
]
|
|
3573
|
+
}
|
|
3574
|
+
),
|
|
3575
|
+
/* @__PURE__ */ jsx27(ChevronDownIcon2, { className: "aui-workforce-selector-icon pointer-events-none absolute right-1 size-3 text-muted-foreground" })
|
|
3576
|
+
] });
|
|
3577
|
+
};
|
|
3578
|
+
function idOf2(item) {
|
|
3579
|
+
return item.id ?? item.uid ?? item.name ?? "";
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
// src/components/chat-shell.tsx
|
|
3583
|
+
import { Fragment as Fragment3, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3584
|
+
var TimbalChatShell = ({
|
|
3585
|
+
workforceId,
|
|
3586
|
+
brand,
|
|
3587
|
+
headerActions,
|
|
3588
|
+
hideWorkforceSelector,
|
|
3589
|
+
className,
|
|
3590
|
+
headerClassName,
|
|
3591
|
+
baseUrl,
|
|
3592
|
+
fetch: fetch2,
|
|
3593
|
+
...chatProps
|
|
3594
|
+
}) => {
|
|
3595
|
+
const {
|
|
3596
|
+
workforces,
|
|
3597
|
+
selectedId,
|
|
3598
|
+
setSelectedId
|
|
3599
|
+
} = useWorkforces({ baseUrl, fetch: fetch2 });
|
|
3600
|
+
const effectiveId = workforceId ?? selectedId;
|
|
3601
|
+
const showSelector = !hideWorkforceSelector && !workforceId && workforces.length > 0;
|
|
3602
|
+
return /* @__PURE__ */ jsxs16(
|
|
3603
|
+
"div",
|
|
3604
|
+
{
|
|
3605
|
+
className: cn(
|
|
3606
|
+
"aui-chat-shell flex h-screen flex-col overflow-hidden",
|
|
3607
|
+
className
|
|
3608
|
+
),
|
|
3609
|
+
children: [
|
|
3610
|
+
/* @__PURE__ */ jsxs16(
|
|
3611
|
+
"header",
|
|
3612
|
+
{
|
|
3613
|
+
className: cn(
|
|
3614
|
+
"aui-chat-shell-header flex shrink-0 items-center justify-between border-b border-border/50 bg-background/90 px-5 py-2 backdrop-blur-md",
|
|
3615
|
+
headerClassName
|
|
3616
|
+
),
|
|
3617
|
+
children: [
|
|
3618
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center", children: [
|
|
3619
|
+
brand,
|
|
3620
|
+
showSelector && /* @__PURE__ */ jsxs16(Fragment3, { children: [
|
|
3621
|
+
/* @__PURE__ */ jsx28("div", { className: "mx-3.5 h-3.5 w-px bg-border" }),
|
|
3622
|
+
/* @__PURE__ */ jsx28(
|
|
3623
|
+
WorkforceSelector,
|
|
3624
|
+
{
|
|
3625
|
+
workforces,
|
|
3626
|
+
value: selectedId,
|
|
3627
|
+
onChange: setSelectedId
|
|
3628
|
+
}
|
|
3629
|
+
)
|
|
3630
|
+
] })
|
|
3631
|
+
] }),
|
|
3632
|
+
/* @__PURE__ */ jsx28("div", { className: "flex items-center gap-0.5", children: headerActions })
|
|
3633
|
+
]
|
|
3634
|
+
}
|
|
3635
|
+
),
|
|
3636
|
+
/* @__PURE__ */ jsx28(
|
|
3637
|
+
TimbalChat,
|
|
3638
|
+
{
|
|
3639
|
+
workforceId: effectiveId,
|
|
3640
|
+
baseUrl,
|
|
3641
|
+
fetch: fetch2,
|
|
3642
|
+
className: "min-h-0 flex-1",
|
|
3643
|
+
...chatProps
|
|
3644
|
+
},
|
|
3645
|
+
effectiveId
|
|
3646
|
+
)
|
|
3647
|
+
]
|
|
3648
|
+
}
|
|
3649
|
+
);
|
|
3650
|
+
};
|
|
3651
|
+
|
|
1521
3652
|
// src/auth/provider.tsx
|
|
1522
3653
|
import {
|
|
1523
|
-
createContext,
|
|
1524
|
-
useCallback as
|
|
1525
|
-
useContext,
|
|
1526
|
-
useEffect as
|
|
1527
|
-
useState as
|
|
3654
|
+
createContext as createContext4,
|
|
3655
|
+
useCallback as useCallback3,
|
|
3656
|
+
useContext as useContext4,
|
|
3657
|
+
useEffect as useEffect6,
|
|
3658
|
+
useState as useState9
|
|
1528
3659
|
} from "react";
|
|
1529
|
-
import { jsx as
|
|
3660
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1530
3661
|
function isInsideIframe() {
|
|
1531
3662
|
try {
|
|
1532
3663
|
return typeof window !== "undefined" && window.self !== window.top;
|
|
@@ -1534,9 +3665,9 @@ function isInsideIframe() {
|
|
|
1534
3665
|
return true;
|
|
1535
3666
|
}
|
|
1536
3667
|
}
|
|
1537
|
-
var SessionContext =
|
|
3668
|
+
var SessionContext = createContext4(void 0);
|
|
1538
3669
|
var useSession = () => {
|
|
1539
|
-
const context =
|
|
3670
|
+
const context = useContext4(SessionContext);
|
|
1540
3671
|
if (context === void 0) {
|
|
1541
3672
|
throw new Error("useSession must be used within a SessionProvider");
|
|
1542
3673
|
}
|
|
@@ -1546,10 +3677,10 @@ var SessionProvider = ({
|
|
|
1546
3677
|
children,
|
|
1547
3678
|
enabled = true
|
|
1548
3679
|
}) => {
|
|
1549
|
-
const [user, setUser] =
|
|
1550
|
-
const [loading, setLoading] =
|
|
1551
|
-
const [embedded] =
|
|
1552
|
-
|
|
3680
|
+
const [user, setUser] = useState9(null);
|
|
3681
|
+
const [loading, setLoading] = useState9(enabled);
|
|
3682
|
+
const [embedded] = useState9(isInsideIframe);
|
|
3683
|
+
useEffect6(() => {
|
|
1553
3684
|
if (!enabled) {
|
|
1554
3685
|
setLoading(false);
|
|
1555
3686
|
return;
|
|
@@ -1610,7 +3741,7 @@ var SessionProvider = ({
|
|
|
1610
3741
|
messageCleanup?.();
|
|
1611
3742
|
};
|
|
1612
3743
|
}, [enabled, embedded]);
|
|
1613
|
-
const logout =
|
|
3744
|
+
const logout = useCallback3(() => {
|
|
1614
3745
|
clearTokens();
|
|
1615
3746
|
setUser(null);
|
|
1616
3747
|
const returnTo = encodeURIComponent(
|
|
@@ -1620,7 +3751,7 @@ var SessionProvider = ({
|
|
|
1620
3751
|
() => window.location.href = `/api/auth/login?return_to=${returnTo}`
|
|
1621
3752
|
);
|
|
1622
3753
|
}, []);
|
|
1623
|
-
return /* @__PURE__ */
|
|
3754
|
+
return /* @__PURE__ */ jsx29(
|
|
1624
3755
|
SessionContext.Provider,
|
|
1625
3756
|
{
|
|
1626
3757
|
value: {
|
|
@@ -1637,7 +3768,7 @@ var SessionProvider = ({
|
|
|
1637
3768
|
|
|
1638
3769
|
// src/auth/guard.tsx
|
|
1639
3770
|
import { Loader2 } from "lucide-react";
|
|
1640
|
-
import { jsx as
|
|
3771
|
+
import { jsx as jsx30 } from "react/jsx-runtime";
|
|
1641
3772
|
var AuthGuard = ({
|
|
1642
3773
|
children,
|
|
1643
3774
|
requireAuth = false,
|
|
@@ -1648,7 +3779,7 @@ var AuthGuard = ({
|
|
|
1648
3779
|
return children;
|
|
1649
3780
|
}
|
|
1650
3781
|
if (loading) {
|
|
1651
|
-
return /* @__PURE__ */
|
|
3782
|
+
return /* @__PURE__ */ jsx30("div", { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx30(Loader2, { className: "w-8 h-8 animate-spin" }) });
|
|
1652
3783
|
}
|
|
1653
3784
|
if (requireAuth && !isAuthenticated && !isEmbedded) {
|
|
1654
3785
|
const returnTo = encodeURIComponent(
|
|
@@ -1660,15 +3791,24 @@ var AuthGuard = ({
|
|
|
1660
3791
|
return children;
|
|
1661
3792
|
};
|
|
1662
3793
|
export {
|
|
3794
|
+
ARTIFACT_AGENT_INSTRUCTIONS,
|
|
3795
|
+
ARTIFACT_FENCE_LANGUAGES,
|
|
1663
3796
|
ActionBarPrimitive2 as ActionBarPrimitive,
|
|
3797
|
+
ArtifactCard,
|
|
3798
|
+
ArtifactRegistryProvider,
|
|
3799
|
+
ArtifactView,
|
|
3800
|
+
AssistantRuntimeProvider2 as AssistantRuntimeProvider,
|
|
1664
3801
|
AuthGuard,
|
|
1665
3802
|
Avatar,
|
|
1666
3803
|
AvatarFallback,
|
|
1667
3804
|
AvatarImage,
|
|
1668
3805
|
Button,
|
|
3806
|
+
ChartArtifactView,
|
|
3807
|
+
Composer,
|
|
1669
3808
|
ComposerAddAttachment,
|
|
1670
3809
|
ComposerAttachments,
|
|
1671
|
-
|
|
3810
|
+
ComposerPrimitive4 as ComposerPrimitive,
|
|
3811
|
+
DEFAULT_UPLOAD_ACCEPT,
|
|
1672
3812
|
Dialog,
|
|
1673
3813
|
DialogClose,
|
|
1674
3814
|
DialogContent,
|
|
@@ -1676,35 +3816,70 @@ export {
|
|
|
1676
3816
|
DialogPortal,
|
|
1677
3817
|
DialogTitle,
|
|
1678
3818
|
DialogTrigger,
|
|
3819
|
+
HtmlArtifactView,
|
|
3820
|
+
JsonArtifactView,
|
|
1679
3821
|
MarkdownText,
|
|
1680
3822
|
MessagePrimitive3 as MessagePrimitive,
|
|
3823
|
+
QuestionArtifactView,
|
|
1681
3824
|
SessionProvider,
|
|
1682
3825
|
Shimmer,
|
|
3826
|
+
Suggestions,
|
|
1683
3827
|
syntax_highlighter_default as SyntaxHighlighter,
|
|
3828
|
+
TableArtifactView,
|
|
1684
3829
|
Thread,
|
|
1685
3830
|
ThreadPrimitive2 as ThreadPrimitive,
|
|
1686
3831
|
TimbalChat,
|
|
3832
|
+
TimbalChatShell,
|
|
1687
3833
|
TimbalRuntimeProvider,
|
|
3834
|
+
ToolArtifactFallback,
|
|
1688
3835
|
ToolFallback,
|
|
1689
3836
|
Tooltip,
|
|
1690
3837
|
TooltipContent,
|
|
1691
3838
|
TooltipIconButton,
|
|
1692
3839
|
TooltipProvider,
|
|
1693
3840
|
TooltipTrigger,
|
|
3841
|
+
UiArtifactView,
|
|
3842
|
+
UiCustomNodeRegistryProvider,
|
|
3843
|
+
UiEventProvider,
|
|
3844
|
+
UiNodeView,
|
|
1694
3845
|
UserMessageAttachments,
|
|
3846
|
+
WorkforceSelector,
|
|
1695
3847
|
authFetch,
|
|
1696
3848
|
buttonVariants,
|
|
1697
3849
|
clearTokens,
|
|
1698
3850
|
cn,
|
|
3851
|
+
createDefaultAttachmentAdapter,
|
|
3852
|
+
createUploadAttachmentAdapter,
|
|
3853
|
+
defaultArtifactRenderers,
|
|
1699
3854
|
fetchCurrentUser,
|
|
3855
|
+
findMarkdownArtifacts,
|
|
1700
3856
|
getAccessToken,
|
|
3857
|
+
getPath,
|
|
1701
3858
|
getRefreshToken,
|
|
3859
|
+
isArtifact,
|
|
3860
|
+
isArtifactFenceLanguage,
|
|
3861
|
+
isUiBinding,
|
|
3862
|
+
parseArtifactFromToolResult,
|
|
3863
|
+
parseSSELine2 as parseSSELine,
|
|
1702
3864
|
refreshAccessToken,
|
|
3865
|
+
resolveAttachmentAdapter,
|
|
3866
|
+
resolveBindable,
|
|
1703
3867
|
setAccessToken,
|
|
3868
|
+
setPath,
|
|
1704
3869
|
setRefreshToken,
|
|
1705
|
-
|
|
3870
|
+
splitMarkdownByArtifacts,
|
|
3871
|
+
useArtifactRegistry,
|
|
3872
|
+
useComposerRuntime2 as useComposerRuntime,
|
|
1706
3873
|
useMessageRuntime,
|
|
3874
|
+
useResolvedSuggestions,
|
|
1707
3875
|
useSession,
|
|
1708
3876
|
useThread,
|
|
1709
|
-
|
|
3877
|
+
useThreadRuntime4 as useThreadRuntime,
|
|
3878
|
+
useTimbalRuntime,
|
|
3879
|
+
useTimbalStream,
|
|
3880
|
+
useUiCustomNodeRegistry,
|
|
3881
|
+
useUiDispatch,
|
|
3882
|
+
useUiEventEmitter,
|
|
3883
|
+
useUiState,
|
|
3884
|
+
useWorkforces
|
|
1710
3885
|
};
|