@cntyclub/agent-react 0.10.1 → 0.11.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/dist/api/schema.d.ts +56 -0
- package/dist/api/schema.d.ts.map +1 -1
- package/dist/api-client.d.ts +4 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/components/agent-composer.d.ts +12 -5
- package/dist/components/agent-composer.d.ts.map +1 -1
- package/dist/components/agent-image-lightbox.d.ts +14 -0
- package/dist/components/agent-image-lightbox.d.ts.map +1 -0
- package/dist/components/agent-panel.d.ts.map +1 -1
- package/dist/components/agent-voice-recorder.d.ts +22 -0
- package/dist/components/agent-voice-recorder.d.ts.map +1 -0
- package/dist/components/agent-widget.d.ts.map +1 -1
- package/dist/components/message-item.d.ts.map +1 -1
- package/dist/file-support.d.ts +23 -5
- package/dist/file-support.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +564 -169
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/use-agent-chat.d.ts +1 -0
- package/dist/use-agent-chat.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import * as
|
|
1
|
+
import { cn, Button, Tooltip, TooltipTrigger, TooltipContent, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, DataTablePaged } from '@cntyclub/ui-react';
|
|
2
|
+
import { XIcon, Trash2Icon, SquareIcon, PauseIcon, PlayIcon, CheckIcon, Loader2Icon, FileTextIcon, PaperclipIcon, MicIcon, ArrowUpIcon, CopyIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
|
|
3
|
+
import * as React11 from 'react';
|
|
4
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
4
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import { motion, AnimatePresence } from 'motion/react';
|
|
6
7
|
|
|
7
8
|
// src/api-client.ts
|
|
8
9
|
var AgentApiError = class extends Error {
|
|
@@ -52,6 +53,18 @@ var AgentApiClient = class {
|
|
|
52
53
|
getConfig() {
|
|
53
54
|
return this.request("/agent-mode/config/");
|
|
54
55
|
}
|
|
56
|
+
/** Upload a recorded voice clip and get back the transcribed text. */
|
|
57
|
+
transcribe(audio) {
|
|
58
|
+
const type = audio.type || "audio/webm";
|
|
59
|
+
const ext = type.includes("mp4") ? "mp4" : type.includes("ogg") ? "ogg" : type.includes("wav") ? "wav" : "webm";
|
|
60
|
+
const form = new FormData();
|
|
61
|
+
form.append("client_id", this.config.clientId);
|
|
62
|
+
form.append("audio", audio, `recording.${ext}`);
|
|
63
|
+
return this.request("/agent-mode/transcribe/", {
|
|
64
|
+
body: form,
|
|
65
|
+
method: "POST"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
55
68
|
sendMessage(message, conversationId, files) {
|
|
56
69
|
if (files && files.length > 0) {
|
|
57
70
|
const form = new FormData();
|
|
@@ -183,28 +196,55 @@ var AgentApiClient = class {
|
|
|
183
196
|
|
|
184
197
|
// src/file-support.ts
|
|
185
198
|
var DEFAULT_ACCEPT = [".txt", ".md", ".markdown", ".csv", ".tsv", ".log", ".docx"];
|
|
199
|
+
var IMAGE_ACCEPT = [".png", ".jpg", ".jpeg", ".webp", ".gif"];
|
|
186
200
|
var DEFAULT_MAX_SIZE_MB = 5;
|
|
201
|
+
var DEFAULT_IMAGE_MAX_SIZE_MB = 10;
|
|
187
202
|
function fileUploadEnabled(config) {
|
|
188
203
|
return config.fileUpload?.enabled !== false;
|
|
189
204
|
}
|
|
190
|
-
function
|
|
205
|
+
function textExtensions(config) {
|
|
206
|
+
if (!fileUploadEnabled(config)) return [];
|
|
191
207
|
const accept = config.fileUpload?.accept;
|
|
192
208
|
return accept && accept.length > 0 ? accept.map((ext) => ext.toLowerCase()) : DEFAULT_ACCEPT;
|
|
193
209
|
}
|
|
210
|
+
function acceptedExtensions(config, caps) {
|
|
211
|
+
const extensions = [...textExtensions(config)];
|
|
212
|
+
if (caps?.visionEnabled) extensions.push(...IMAGE_ACCEPT);
|
|
213
|
+
return extensions;
|
|
214
|
+
}
|
|
194
215
|
function maxSizeMb(config) {
|
|
195
216
|
return config.fileUpload?.maxSizeMb ?? DEFAULT_MAX_SIZE_MB;
|
|
196
217
|
}
|
|
197
|
-
function
|
|
198
|
-
return
|
|
218
|
+
function attachmentsEnabled(config, caps) {
|
|
219
|
+
return fileUploadEnabled(config) || Boolean(caps?.visionEnabled);
|
|
220
|
+
}
|
|
221
|
+
function acceptAttribute(config, caps) {
|
|
222
|
+
return acceptedExtensions(config, caps).join(",");
|
|
199
223
|
}
|
|
200
224
|
function extensionOf(filename) {
|
|
201
225
|
const dot = filename.lastIndexOf(".");
|
|
202
226
|
return dot >= 0 ? filename.slice(dot).toLowerCase() : "";
|
|
203
227
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
228
|
+
function isImageFile(file) {
|
|
229
|
+
return file.type.startsWith("image/") || IMAGE_ACCEPT.includes(extensionOf(file.name));
|
|
230
|
+
}
|
|
231
|
+
function validateFile(config, file, caps) {
|
|
232
|
+
if (isImageFile(file)) {
|
|
233
|
+
if (!caps?.visionEnabled) {
|
|
234
|
+
return `\u201C${file.name}\u201D is an image \u2014 this assistant can\u2019t accept images.`;
|
|
235
|
+
}
|
|
236
|
+
const limit2 = caps.visionMaxMb ?? DEFAULT_IMAGE_MAX_SIZE_MB;
|
|
237
|
+
if (file.size > limit2 * 1024 * 1024) {
|
|
238
|
+
return `\u201C${file.name}\u201D is too large (max ${limit2} MB).`;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const extensions = textExtensions(config);
|
|
243
|
+
if (!fileUploadEnabled(config)) {
|
|
244
|
+
return `\u201C${file.name}\u201D can\u2019t be attached \u2014 file upload is disabled for this assistant.`;
|
|
245
|
+
}
|
|
206
246
|
if (!extensions.includes(extensionOf(file.name))) {
|
|
207
|
-
return `\u201C${file.name}\u201D isn\u2019t a supported
|
|
247
|
+
return `\u201C${file.name}\u201D isn\u2019t a supported file. Allowed: ${extensions.join(", ")}.`;
|
|
208
248
|
}
|
|
209
249
|
const limit = maxSizeMb(config);
|
|
210
250
|
if (file.size > limit * 1024 * 1024) {
|
|
@@ -218,42 +258,305 @@ function formatBytes(bytes) {
|
|
|
218
258
|
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
219
259
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
220
260
|
}
|
|
261
|
+
function AgentImageLightbox({ src, alt, onClose }) {
|
|
262
|
+
React11.useEffect(() => {
|
|
263
|
+
if (!src) return;
|
|
264
|
+
const onKey = (event) => {
|
|
265
|
+
if (event.key === "Escape") onClose();
|
|
266
|
+
};
|
|
267
|
+
document.addEventListener("keydown", onKey);
|
|
268
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
269
|
+
}, [src, onClose]);
|
|
270
|
+
if (typeof document === "undefined") return null;
|
|
271
|
+
return createPortal(
|
|
272
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: src ? /* @__PURE__ */ jsxs(
|
|
273
|
+
motion.div,
|
|
274
|
+
{
|
|
275
|
+
animate: { opacity: 1 },
|
|
276
|
+
"aria-modal": "true",
|
|
277
|
+
className: cn(
|
|
278
|
+
"fixed inset-0 z-[2147483000] flex items-center justify-center bg-black/80 p-6 backdrop-blur-sm"
|
|
279
|
+
),
|
|
280
|
+
exit: { opacity: 0 },
|
|
281
|
+
initial: { opacity: 0 },
|
|
282
|
+
onClick: onClose,
|
|
283
|
+
role: "dialog",
|
|
284
|
+
transition: { duration: 0.15 },
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ jsx(
|
|
287
|
+
"button",
|
|
288
|
+
{
|
|
289
|
+
"aria-label": "Close image",
|
|
290
|
+
className: "absolute top-4 right-4 flex size-10 cursor-pointer items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 [&_svg]:size-5",
|
|
291
|
+
onClick: onClose,
|
|
292
|
+
type: "button",
|
|
293
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
294
|
+
}
|
|
295
|
+
),
|
|
296
|
+
/* @__PURE__ */ jsx(
|
|
297
|
+
motion.img,
|
|
298
|
+
{
|
|
299
|
+
alt: alt ?? "Attached image",
|
|
300
|
+
animate: { scale: 1, opacity: 1 },
|
|
301
|
+
className: "max-h-full max-w-full rounded-lg object-contain shadow-2xl",
|
|
302
|
+
exit: { scale: 0.96, opacity: 0 },
|
|
303
|
+
initial: { scale: 0.96, opacity: 0 },
|
|
304
|
+
onClick: (event) => event.stopPropagation(),
|
|
305
|
+
src,
|
|
306
|
+
transition: { duration: 0.15 }
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
) : null }),
|
|
312
|
+
document.body
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
function formatClock(totalSeconds) {
|
|
316
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
317
|
+
const seconds = totalSeconds % 60;
|
|
318
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
319
|
+
}
|
|
320
|
+
function pickMimeType() {
|
|
321
|
+
if (typeof MediaRecorder === "undefined") return "";
|
|
322
|
+
for (const type of ["audio/webm", "audio/mp4", "audio/ogg"]) {
|
|
323
|
+
if (MediaRecorder.isTypeSupported(type)) return type;
|
|
324
|
+
}
|
|
325
|
+
return "";
|
|
326
|
+
}
|
|
327
|
+
function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError }) {
|
|
328
|
+
const [phase, setPhase] = React11.useState("recording");
|
|
329
|
+
const [elapsed, setElapsed] = React11.useState(0);
|
|
330
|
+
const [playing, setPlaying] = React11.useState(false);
|
|
331
|
+
const recorderRef = React11.useRef(null);
|
|
332
|
+
const streamRef = React11.useRef(null);
|
|
333
|
+
const chunksRef = React11.useRef([]);
|
|
334
|
+
const blobRef = React11.useRef(null);
|
|
335
|
+
const audioUrlRef = React11.useRef(null);
|
|
336
|
+
const audioRef = React11.useRef(null);
|
|
337
|
+
const timerRef = React11.useRef(null);
|
|
338
|
+
const onErrorRef = React11.useRef(onError);
|
|
339
|
+
onErrorRef.current = onError;
|
|
340
|
+
const stopTracks = React11.useCallback(() => {
|
|
341
|
+
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
342
|
+
streamRef.current = null;
|
|
343
|
+
if (timerRef.current) {
|
|
344
|
+
clearInterval(timerRef.current);
|
|
345
|
+
timerRef.current = null;
|
|
346
|
+
}
|
|
347
|
+
}, []);
|
|
348
|
+
React11.useEffect(() => {
|
|
349
|
+
let cancelled = false;
|
|
350
|
+
void (async () => {
|
|
351
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
352
|
+
onErrorRef.current("Voice recording isn't supported in this browser.");
|
|
353
|
+
onCancel();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
358
|
+
if (cancelled) {
|
|
359
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
streamRef.current = stream;
|
|
363
|
+
const mimeType = pickMimeType();
|
|
364
|
+
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
|
|
365
|
+
recorderRef.current = recorder;
|
|
366
|
+
chunksRef.current = [];
|
|
367
|
+
recorder.ondataavailable = (event) => {
|
|
368
|
+
if (event.data.size > 0) chunksRef.current.push(event.data);
|
|
369
|
+
};
|
|
370
|
+
recorder.onstop = () => {
|
|
371
|
+
const blob = new Blob(chunksRef.current, { type: mimeType || "audio/webm" });
|
|
372
|
+
blobRef.current = blob;
|
|
373
|
+
if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
|
|
374
|
+
audioUrlRef.current = URL.createObjectURL(blob);
|
|
375
|
+
setPhase("review");
|
|
376
|
+
};
|
|
377
|
+
recorder.start();
|
|
378
|
+
timerRef.current = setInterval(() => {
|
|
379
|
+
setElapsed((current) => {
|
|
380
|
+
const next = current + 1;
|
|
381
|
+
if (next >= maxSeconds) stopRecording();
|
|
382
|
+
return next;
|
|
383
|
+
});
|
|
384
|
+
}, 1e3);
|
|
385
|
+
} catch {
|
|
386
|
+
onErrorRef.current("Microphone access was blocked. Enable it to record.");
|
|
387
|
+
onCancel();
|
|
388
|
+
}
|
|
389
|
+
})();
|
|
390
|
+
return () => {
|
|
391
|
+
cancelled = true;
|
|
392
|
+
stopTracks();
|
|
393
|
+
if (recorderRef.current && recorderRef.current.state !== "inactive") {
|
|
394
|
+
try {
|
|
395
|
+
recorderRef.current.stop();
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
|
|
400
|
+
};
|
|
401
|
+
}, []);
|
|
402
|
+
function stopRecording() {
|
|
403
|
+
if (timerRef.current) {
|
|
404
|
+
clearInterval(timerRef.current);
|
|
405
|
+
timerRef.current = null;
|
|
406
|
+
}
|
|
407
|
+
const recorder = recorderRef.current;
|
|
408
|
+
if (recorder && recorder.state !== "inactive") recorder.stop();
|
|
409
|
+
stopTracks();
|
|
410
|
+
}
|
|
411
|
+
const togglePlayback = () => {
|
|
412
|
+
const audio = audioRef.current;
|
|
413
|
+
if (!audio) return;
|
|
414
|
+
if (audio.paused) {
|
|
415
|
+
void audio.play();
|
|
416
|
+
setPlaying(true);
|
|
417
|
+
} else {
|
|
418
|
+
audio.pause();
|
|
419
|
+
setPlaying(false);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const confirm = async () => {
|
|
423
|
+
const blob = blobRef.current;
|
|
424
|
+
if (!blob || blob.size === 0) {
|
|
425
|
+
onError("The recording was empty.");
|
|
426
|
+
onCancel();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
setPhase("transcribing");
|
|
430
|
+
try {
|
|
431
|
+
const text = await transcribe(blob);
|
|
432
|
+
onDone(text.trim());
|
|
433
|
+
} catch {
|
|
434
|
+
onError("Transcription failed. Please try again.");
|
|
435
|
+
setPhase("review");
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
return /* @__PURE__ */ jsxs(
|
|
439
|
+
motion.div,
|
|
440
|
+
{
|
|
441
|
+
animate: { opacity: 1, y: 0 },
|
|
442
|
+
className: "flex min-h-8 flex-1 items-center gap-2 px-1",
|
|
443
|
+
initial: { opacity: 0, y: 4 },
|
|
444
|
+
transition: { duration: 0.15 },
|
|
445
|
+
children: [
|
|
446
|
+
phase === "recording" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
447
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm", children: [
|
|
448
|
+
/* @__PURE__ */ jsx(
|
|
449
|
+
motion.span,
|
|
450
|
+
{
|
|
451
|
+
animate: { opacity: [1, 0.35, 1] },
|
|
452
|
+
className: "size-2.5 rounded-full bg-destructive",
|
|
453
|
+
transition: { duration: 1.2, repeat: Infinity }
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatClock(elapsed) }),
|
|
457
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
458
|
+
"/ ",
|
|
459
|
+
formatClock(maxSeconds)
|
|
460
|
+
] })
|
|
461
|
+
] }),
|
|
462
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
463
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Discard recording", onClick: onCancel, size: "icon-sm", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
|
|
464
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Stop recording", onClick: stopRecording, size: "icon-sm", type: "button", children: /* @__PURE__ */ jsx(SquareIcon, {}) })
|
|
465
|
+
] }) : null,
|
|
466
|
+
phase === "review" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
467
|
+
/* @__PURE__ */ jsx("audio", { onEnded: () => setPlaying(false), ref: audioRef, src: audioUrlRef.current ?? void 0 }),
|
|
468
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": playing ? "Pause" : "Play recording", onClick: togglePlayback, size: "icon-sm", type: "button", variant: "ghost", children: playing ? /* @__PURE__ */ jsx(PauseIcon, {}) : /* @__PURE__ */ jsx(PlayIcon, {}) }),
|
|
469
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm tabular-nums", children: formatClock(elapsed) }),
|
|
470
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
471
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Discard recording", onClick: onCancel, size: "icon-sm", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
|
|
472
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Use recording", className: "rounded-full before:rounded-full", onClick: () => void confirm(), size: "icon-sm", type: "button", children: /* @__PURE__ */ jsx(CheckIcon, {}) })
|
|
473
|
+
] }) : null,
|
|
474
|
+
phase === "transcribing" ? /* @__PURE__ */ jsxs("span", { className: cn("flex flex-1 items-center gap-2 text-muted-foreground text-sm"), children: [
|
|
475
|
+
/* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" }),
|
|
476
|
+
"Transcribing\u2026"
|
|
477
|
+
] }) : null
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
function appendText(current, addition) {
|
|
483
|
+
if (!addition) return current;
|
|
484
|
+
if (!current) return addition;
|
|
485
|
+
return /\s$/.test(current) ? current + addition : `${current} ${addition}`;
|
|
486
|
+
}
|
|
221
487
|
function AgentComposer({
|
|
222
488
|
acceptAttribute: acceptAttribute2,
|
|
223
489
|
busy,
|
|
224
490
|
className,
|
|
225
491
|
disabled,
|
|
226
492
|
files,
|
|
493
|
+
onError,
|
|
227
494
|
onPickFiles,
|
|
228
495
|
onRemoveFile,
|
|
229
496
|
onSubmit,
|
|
497
|
+
onTranscribe,
|
|
230
498
|
onValueChange,
|
|
231
499
|
placeholder = "Ask anything\u2026",
|
|
232
|
-
value
|
|
500
|
+
value,
|
|
501
|
+
voiceEnabled,
|
|
502
|
+
voiceMaxSeconds = 600
|
|
233
503
|
}) {
|
|
234
|
-
const textareaRef =
|
|
235
|
-
const inputRef =
|
|
504
|
+
const textareaRef = React11.useRef(null);
|
|
505
|
+
const inputRef = React11.useRef(null);
|
|
506
|
+
const [recording, setRecording] = React11.useState(false);
|
|
507
|
+
const [lightboxSrc, setLightboxSrc] = React11.useState(null);
|
|
236
508
|
const canSubmit = !disabled && !busy && (value.trim().length > 0 || files.length > 0);
|
|
237
|
-
const
|
|
509
|
+
const voiceAvailable = Boolean(voiceEnabled && onTranscribe);
|
|
510
|
+
const submit = React11.useCallback(() => {
|
|
238
511
|
if (!canSubmit) return;
|
|
239
512
|
onSubmit(value.trim());
|
|
240
513
|
textareaRef.current?.focus();
|
|
241
514
|
}, [canSubmit, onSubmit, value]);
|
|
242
|
-
|
|
515
|
+
React11.useEffect(() => {
|
|
243
516
|
if (!disabled) textareaRef.current?.focus();
|
|
244
517
|
}, [disabled]);
|
|
245
|
-
|
|
518
|
+
React11.useEffect(() => {
|
|
246
519
|
const textarea = textareaRef.current;
|
|
247
520
|
if (!textarea) return;
|
|
248
521
|
textarea.style.height = "auto";
|
|
249
522
|
textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`;
|
|
250
523
|
}, [value]);
|
|
524
|
+
const previews = React11.useMemo(
|
|
525
|
+
() => files.map((file, originalIndex) => {
|
|
526
|
+
const image = isImageFile(file);
|
|
527
|
+
return { file, image, originalIndex, url: image ? URL.createObjectURL(file) : null };
|
|
528
|
+
}),
|
|
529
|
+
[files]
|
|
530
|
+
);
|
|
531
|
+
React11.useEffect(
|
|
532
|
+
() => () => {
|
|
533
|
+
previews.forEach((preview) => preview.url && URL.revokeObjectURL(preview.url));
|
|
534
|
+
},
|
|
535
|
+
[previews]
|
|
536
|
+
);
|
|
537
|
+
const ordered = React11.useMemo(
|
|
538
|
+
() => [...previews].sort((a, b) => Number(b.image) - Number(a.image)),
|
|
539
|
+
[previews]
|
|
540
|
+
);
|
|
251
541
|
const handlePick = (event) => {
|
|
252
542
|
const picked = Array.from(event.target.files ?? []);
|
|
253
543
|
if (picked.length) onPickFiles?.(picked);
|
|
254
544
|
event.target.value = "";
|
|
255
545
|
};
|
|
256
|
-
|
|
546
|
+
const handlePaste = (event) => {
|
|
547
|
+
if (!onPickFiles) return;
|
|
548
|
+
const images = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
|
|
549
|
+
if (images.length) {
|
|
550
|
+
event.preventDefault();
|
|
551
|
+
onPickFiles(images);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
const finishRecording = (text) => {
|
|
555
|
+
setRecording(false);
|
|
556
|
+
if (text) onValueChange(appendText(value, text));
|
|
557
|
+
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
558
|
+
};
|
|
559
|
+
return /* @__PURE__ */ jsxs(
|
|
257
560
|
"form",
|
|
258
561
|
{
|
|
259
562
|
className: cn("shrink-0 bg-background p-3 pt-1.5", className),
|
|
@@ -262,96 +565,148 @@ function AgentComposer({
|
|
|
262
565
|
event.preventDefault();
|
|
263
566
|
submit();
|
|
264
567
|
},
|
|
265
|
-
children:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
568
|
+
children: [
|
|
569
|
+
/* @__PURE__ */ jsxs(
|
|
570
|
+
"div",
|
|
571
|
+
{
|
|
572
|
+
className: cn(
|
|
573
|
+
"flex flex-col gap-1.5 rounded-2xl border border-input bg-popover p-1.5 shadow-xs transition-[border-color,box-shadow] duration-150 focus-within:border-ring/64 focus-within:ring-2 focus-within:ring-ring/24 dark:bg-input/32",
|
|
574
|
+
disabled && "opacity-64"
|
|
575
|
+
),
|
|
576
|
+
children: [
|
|
577
|
+
files.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pt-1", children: ordered.map(
|
|
578
|
+
(preview) => preview.image && preview.url ? /* @__PURE__ */ jsxs("span", { className: "group relative", children: [
|
|
579
|
+
/* @__PURE__ */ jsx(
|
|
580
|
+
"button",
|
|
581
|
+
{
|
|
582
|
+
"aria-label": `Preview ${preview.file.name}`,
|
|
583
|
+
className: "block size-14 cursor-zoom-in overflow-hidden rounded-lg border border-border/72",
|
|
584
|
+
onClick: () => setLightboxSrc(preview.url),
|
|
585
|
+
type: "button",
|
|
586
|
+
children: /* @__PURE__ */ jsx("img", { alt: preview.file.name, className: "size-full object-cover", src: preview.url })
|
|
587
|
+
}
|
|
588
|
+
),
|
|
281
589
|
onRemoveFile ? /* @__PURE__ */ jsx(
|
|
282
590
|
"button",
|
|
283
591
|
{
|
|
284
|
-
"aria-label": `Remove ${file.name}`,
|
|
285
|
-
className: "flex size-
|
|
286
|
-
onClick: () => onRemoveFile(
|
|
592
|
+
"aria-label": `Remove ${preview.file.name}`,
|
|
593
|
+
className: "-right-1.5 -top-1.5 absolute flex size-5 cursor-pointer items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-sm hover:text-foreground [&_svg]:size-3",
|
|
594
|
+
onClick: () => onRemoveFile(preview.originalIndex),
|
|
287
595
|
type: "button",
|
|
288
596
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
289
597
|
}
|
|
290
598
|
) : null
|
|
291
|
-
]
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
599
|
+
] }, `${preview.file.name}-${preview.originalIndex}`) : /* @__PURE__ */ jsxs(
|
|
600
|
+
"span",
|
|
601
|
+
{
|
|
602
|
+
className: "flex max-w-52 items-center gap-1.5 rounded-lg border border-border/72 bg-muted/48 py-1 pr-1 pl-2 text-xs",
|
|
603
|
+
children: [
|
|
604
|
+
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
|
|
605
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: preview.file.name }),
|
|
606
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatBytes(preview.file.size) }),
|
|
607
|
+
onRemoveFile ? /* @__PURE__ */ jsx(
|
|
608
|
+
"button",
|
|
609
|
+
{
|
|
610
|
+
"aria-label": `Remove ${preview.file.name}`,
|
|
611
|
+
className: "flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground [&_svg]:size-3",
|
|
612
|
+
onClick: () => onRemoveFile(preview.originalIndex),
|
|
613
|
+
type: "button",
|
|
614
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
615
|
+
}
|
|
616
|
+
) : null
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
`${preview.file.name}-${preview.originalIndex}`
|
|
620
|
+
)
|
|
621
|
+
) }) : null,
|
|
622
|
+
recording && onTranscribe ? /* @__PURE__ */ jsx(
|
|
623
|
+
AgentVoiceRecorder,
|
|
624
|
+
{
|
|
625
|
+
maxSeconds: voiceMaxSeconds,
|
|
626
|
+
onCancel: () => {
|
|
627
|
+
setRecording(false);
|
|
628
|
+
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
629
|
+
},
|
|
630
|
+
onDone: finishRecording,
|
|
631
|
+
onError: (message) => onError?.(message),
|
|
632
|
+
transcribe: onTranscribe
|
|
633
|
+
}
|
|
634
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5", children: [
|
|
635
|
+
onPickFiles ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
636
|
+
/* @__PURE__ */ jsx(
|
|
637
|
+
"input",
|
|
638
|
+
{
|
|
639
|
+
accept: acceptAttribute2,
|
|
640
|
+
className: "hidden",
|
|
641
|
+
multiple: true,
|
|
642
|
+
onChange: handlePick,
|
|
643
|
+
ref: inputRef,
|
|
644
|
+
type: "file"
|
|
645
|
+
}
|
|
646
|
+
),
|
|
647
|
+
/* @__PURE__ */ jsx(
|
|
648
|
+
Button,
|
|
649
|
+
{
|
|
650
|
+
"aria-label": "Attach files",
|
|
651
|
+
className: "shrink-0 self-center",
|
|
652
|
+
disabled,
|
|
653
|
+
onClick: () => inputRef.current?.click(),
|
|
654
|
+
size: "icon-sm",
|
|
655
|
+
type: "button",
|
|
656
|
+
variant: "ghost",
|
|
657
|
+
children: /* @__PURE__ */ jsx(PaperclipIcon, {})
|
|
658
|
+
}
|
|
659
|
+
)
|
|
660
|
+
] }) : null,
|
|
297
661
|
/* @__PURE__ */ jsx(
|
|
298
|
-
"
|
|
662
|
+
"textarea",
|
|
299
663
|
{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
664
|
+
className: "max-h-36 min-h-8 flex-1 resize-none self-center bg-transparent py-1.5 pl-1.5 text-sm outline-none placeholder:text-muted-foreground",
|
|
665
|
+
disabled,
|
|
666
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
667
|
+
onKeyDown: (event) => {
|
|
668
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
669
|
+
event.preventDefault();
|
|
670
|
+
submit();
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
onPaste: onPickFiles ? handlePaste : void 0,
|
|
674
|
+
placeholder,
|
|
675
|
+
ref: textareaRef,
|
|
676
|
+
rows: 1,
|
|
677
|
+
value
|
|
306
678
|
}
|
|
307
679
|
),
|
|
308
|
-
/* @__PURE__ */ jsx(
|
|
680
|
+
voiceAvailable ? /* @__PURE__ */ jsx(
|
|
309
681
|
Button,
|
|
310
682
|
{
|
|
311
|
-
"aria-label": "
|
|
683
|
+
"aria-label": "Record voice",
|
|
312
684
|
className: "shrink-0 self-center",
|
|
313
685
|
disabled,
|
|
314
|
-
onClick: () =>
|
|
686
|
+
onClick: () => setRecording(true),
|
|
315
687
|
size: "icon-sm",
|
|
316
688
|
type: "button",
|
|
317
689
|
variant: "ghost",
|
|
318
|
-
children: /* @__PURE__ */ jsx(
|
|
690
|
+
children: /* @__PURE__ */ jsx(MicIcon, {})
|
|
691
|
+
}
|
|
692
|
+
) : null,
|
|
693
|
+
/* @__PURE__ */ jsx(
|
|
694
|
+
Button,
|
|
695
|
+
{
|
|
696
|
+
"aria-label": "Send message",
|
|
697
|
+
className: "shrink-0 rounded-full before:rounded-full",
|
|
698
|
+
disabled: !canSubmit,
|
|
699
|
+
size: "icon-sm",
|
|
700
|
+
type: "submit",
|
|
701
|
+
children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
|
|
319
702
|
}
|
|
320
703
|
)
|
|
321
|
-
] })
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
onChange: (event) => onValueChange(event.target.value),
|
|
328
|
-
onKeyDown: (event) => {
|
|
329
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
330
|
-
event.preventDefault();
|
|
331
|
-
submit();
|
|
332
|
-
}
|
|
333
|
-
},
|
|
334
|
-
placeholder,
|
|
335
|
-
ref: textareaRef,
|
|
336
|
-
rows: 1,
|
|
337
|
-
value
|
|
338
|
-
}
|
|
339
|
-
),
|
|
340
|
-
/* @__PURE__ */ jsx(
|
|
341
|
-
Button,
|
|
342
|
-
{
|
|
343
|
-
"aria-label": "Send message",
|
|
344
|
-
className: "shrink-0 rounded-full before:rounded-full",
|
|
345
|
-
disabled: !canSubmit,
|
|
346
|
-
size: "icon-sm",
|
|
347
|
-
type: "submit",
|
|
348
|
-
children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
|
|
349
|
-
}
|
|
350
|
-
)
|
|
351
|
-
] })
|
|
352
|
-
]
|
|
353
|
-
}
|
|
354
|
-
)
|
|
704
|
+
] })
|
|
705
|
+
]
|
|
706
|
+
}
|
|
707
|
+
),
|
|
708
|
+
/* @__PURE__ */ jsx(AgentImageLightbox, { onClose: () => setLightboxSrc(null), src: lightboxSrc })
|
|
709
|
+
]
|
|
355
710
|
}
|
|
356
711
|
);
|
|
357
712
|
}
|
|
@@ -424,15 +779,15 @@ function groupMessages(messages) {
|
|
|
424
779
|
return items;
|
|
425
780
|
}
|
|
426
781
|
function ChatScroller({ children, className, ...props }) {
|
|
427
|
-
const viewportRef =
|
|
428
|
-
const pinnedRef =
|
|
429
|
-
const handleScroll =
|
|
782
|
+
const viewportRef = React11.useRef(null);
|
|
783
|
+
const pinnedRef = React11.useRef(true);
|
|
784
|
+
const handleScroll = React11.useCallback(() => {
|
|
430
785
|
const viewport = viewportRef.current;
|
|
431
786
|
if (!viewport) return;
|
|
432
787
|
const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
433
788
|
pinnedRef.current = distanceFromBottom < 64;
|
|
434
789
|
}, []);
|
|
435
|
-
|
|
790
|
+
React11.useEffect(() => {
|
|
436
791
|
const viewport = viewportRef.current;
|
|
437
792
|
if (!viewport || !pinnedRef.current) return;
|
|
438
793
|
viewport.scrollTop = viewport.scrollHeight;
|
|
@@ -450,14 +805,14 @@ function ChatScroller({ children, className, ...props }) {
|
|
|
450
805
|
);
|
|
451
806
|
}
|
|
452
807
|
function CopyButton({ className, text }) {
|
|
453
|
-
const [copied, setCopied] =
|
|
454
|
-
const timerRef =
|
|
455
|
-
|
|
808
|
+
const [copied, setCopied] = React11.useState(false);
|
|
809
|
+
const timerRef = React11.useRef(null);
|
|
810
|
+
React11.useEffect(() => {
|
|
456
811
|
return () => {
|
|
457
812
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
458
813
|
};
|
|
459
814
|
}, []);
|
|
460
|
-
const copy =
|
|
815
|
+
const copy = React11.useCallback(async () => {
|
|
461
816
|
try {
|
|
462
817
|
await navigator.clipboard.writeText(text);
|
|
463
818
|
} catch {
|
|
@@ -488,11 +843,26 @@ function CopyButton({ className, text }) {
|
|
|
488
843
|
/* @__PURE__ */ jsx(TooltipContent, { children: copied ? "Copied!" : "Copy" })
|
|
489
844
|
] });
|
|
490
845
|
}
|
|
846
|
+
var isImageAttachment = (format) => IMAGE_ACCEPT.includes(`.${format.toLowerCase()}`);
|
|
491
847
|
function MessageItem({ message }) {
|
|
848
|
+
const [lightboxSrc, setLightboxSrc] = React11.useState(null);
|
|
492
849
|
if (message.role === "user") {
|
|
493
850
|
const attachments = message.attachments ?? [];
|
|
851
|
+
const images = attachments.filter((a) => isImageAttachment(a.format) && a.url);
|
|
852
|
+
const others = attachments.filter((a) => !isImageAttachment(a.format) || !a.url);
|
|
494
853
|
return /* @__PURE__ */ jsxs(ChatMessage, { from: "user", children: [
|
|
495
|
-
|
|
854
|
+
images.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-end gap-1.5", children: images.map((attachment) => /* @__PURE__ */ jsx(
|
|
855
|
+
"button",
|
|
856
|
+
{
|
|
857
|
+
"aria-label": `Preview ${attachment.filename}`,
|
|
858
|
+
className: "block size-20 cursor-zoom-in overflow-hidden rounded-lg border border-border/72",
|
|
859
|
+
onClick: () => setLightboxSrc(attachment.url ?? null),
|
|
860
|
+
type: "button",
|
|
861
|
+
children: /* @__PURE__ */ jsx("img", { alt: attachment.filename, className: "size-full object-cover", src: attachment.url ?? void 0 })
|
|
862
|
+
},
|
|
863
|
+
attachment.id
|
|
864
|
+
)) }) : null,
|
|
865
|
+
others.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-end gap-1.5", children: others.map((attachment) => /* @__PURE__ */ jsxs(
|
|
496
866
|
"span",
|
|
497
867
|
{
|
|
498
868
|
className: "flex max-w-52 items-center gap-1.5 rounded-lg border border-border/72 bg-muted/48 px-2 py-1 text-muted-foreground text-xs",
|
|
@@ -503,7 +873,8 @@ function MessageItem({ message }) {
|
|
|
503
873
|
},
|
|
504
874
|
attachment.id
|
|
505
875
|
)) }) : null,
|
|
506
|
-
message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) : null
|
|
876
|
+
message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) : null,
|
|
877
|
+
/* @__PURE__ */ jsx(AgentImageLightbox, { onClose: () => setLightboxSrc(null), src: lightboxSrc })
|
|
507
878
|
] });
|
|
508
879
|
}
|
|
509
880
|
if (message.role === "assistant") {
|
|
@@ -534,8 +905,8 @@ var DEFAULT_PHRASES = [
|
|
|
534
905
|
];
|
|
535
906
|
function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
|
|
536
907
|
const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
|
|
537
|
-
const [index, setIndex] =
|
|
538
|
-
|
|
908
|
+
const [index, setIndex] = React11.useState(0);
|
|
909
|
+
React11.useEffect(() => {
|
|
539
910
|
setIndex(0);
|
|
540
911
|
const timer = setInterval(() => {
|
|
541
912
|
setIndex((current) => (current + 1) % words.length);
|
|
@@ -797,7 +1168,7 @@ function DownloadButton({ block, size = "sm" }) {
|
|
|
797
1168
|
);
|
|
798
1169
|
}
|
|
799
1170
|
function DocumentViewer({ block, onClose }) {
|
|
800
|
-
|
|
1171
|
+
React11.useEffect(() => {
|
|
801
1172
|
const onKey = (event) => event.key === "Escape" && onClose();
|
|
802
1173
|
window.addEventListener("keydown", onKey);
|
|
803
1174
|
return () => window.removeEventListener("keydown", onKey);
|
|
@@ -822,7 +1193,7 @@ function DocumentViewer({ block, onClose }) {
|
|
|
822
1193
|
] });
|
|
823
1194
|
}
|
|
824
1195
|
function DocumentCard({ block }) {
|
|
825
|
-
const [open, setOpen] =
|
|
1196
|
+
const [open, setOpen] = React11.useState(false);
|
|
826
1197
|
const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
|
|
827
1198
|
return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
|
|
828
1199
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
|
|
@@ -944,7 +1315,7 @@ function StepDetail({
|
|
|
944
1315
|
] });
|
|
945
1316
|
}
|
|
946
1317
|
function ToolActivityGroup({ describeToolCall, steps }) {
|
|
947
|
-
const [open, setOpen] =
|
|
1318
|
+
const [open, setOpen] = React11.useState(false);
|
|
948
1319
|
if (steps.length === 0) return null;
|
|
949
1320
|
const last = steps[steps.length - 1];
|
|
950
1321
|
const anyRunning = steps.some((step) => step.status === "running");
|
|
@@ -1048,24 +1419,28 @@ function AgentPanel({
|
|
|
1048
1419
|
onClose,
|
|
1049
1420
|
onToggleAgentMode
|
|
1050
1421
|
}) {
|
|
1051
|
-
const [draft, setDraft] =
|
|
1052
|
-
const [view, setView] =
|
|
1053
|
-
const [files, setFiles] =
|
|
1054
|
-
const [uploadError, setUploadError] =
|
|
1055
|
-
const [dragging, setDragging] =
|
|
1056
|
-
const dragDepth =
|
|
1422
|
+
const [draft, setDraft] = React11.useState("");
|
|
1423
|
+
const [view, setView] = React11.useState("chat");
|
|
1424
|
+
const [files, setFiles] = React11.useState([]);
|
|
1425
|
+
const [uploadError, setUploadError] = React11.useState(null);
|
|
1426
|
+
const [dragging, setDragging] = React11.useState(false);
|
|
1427
|
+
const dragDepth = React11.useRef(0);
|
|
1057
1428
|
const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
|
|
1058
|
-
const
|
|
1429
|
+
const caps = {
|
|
1430
|
+
visionEnabled: chat.agentInfo?.vision_enabled,
|
|
1431
|
+
visionMaxMb: chat.agentInfo?.vision_max_file_mb
|
|
1432
|
+
};
|
|
1433
|
+
const uploadEnabled = attachmentsEnabled(config, caps);
|
|
1059
1434
|
const suggestions = config.suggestions ?? [
|
|
1060
1435
|
"What can you help me with?",
|
|
1061
1436
|
"Give me a quick summary of my data."
|
|
1062
1437
|
];
|
|
1063
|
-
const addFiles =
|
|
1438
|
+
const addFiles = React11.useCallback(
|
|
1064
1439
|
(incoming) => {
|
|
1065
1440
|
const accepted = [];
|
|
1066
1441
|
let rejection = null;
|
|
1067
1442
|
for (const file of incoming) {
|
|
1068
|
-
const error = validateFile(config, file);
|
|
1443
|
+
const error = validateFile(config, file, caps);
|
|
1069
1444
|
if (error) {
|
|
1070
1445
|
rejection = error;
|
|
1071
1446
|
continue;
|
|
@@ -1075,12 +1450,12 @@ function AgentPanel({
|
|
|
1075
1450
|
setUploadError(rejection);
|
|
1076
1451
|
if (accepted.length) setFiles((current) => [...current, ...accepted].slice(0, 10));
|
|
1077
1452
|
},
|
|
1078
|
-
[config]
|
|
1453
|
+
[config, caps.visionEnabled, caps.visionMaxMb]
|
|
1079
1454
|
);
|
|
1080
|
-
const removeFile =
|
|
1455
|
+
const removeFile = React11.useCallback((index) => {
|
|
1081
1456
|
setFiles((current) => current.filter((_, i) => i !== index));
|
|
1082
1457
|
}, []);
|
|
1083
|
-
const handleSubmit =
|
|
1458
|
+
const handleSubmit = React11.useCallback(
|
|
1084
1459
|
(text) => {
|
|
1085
1460
|
const staged = files;
|
|
1086
1461
|
setDraft("");
|
|
@@ -1090,7 +1465,7 @@ function AgentPanel({
|
|
|
1090
1465
|
},
|
|
1091
1466
|
[chat, files]
|
|
1092
1467
|
);
|
|
1093
|
-
const onDragEnter =
|
|
1468
|
+
const onDragEnter = React11.useCallback(
|
|
1094
1469
|
(event) => {
|
|
1095
1470
|
if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
|
|
1096
1471
|
dragDepth.current += 1;
|
|
@@ -1098,12 +1473,12 @@ function AgentPanel({
|
|
|
1098
1473
|
},
|
|
1099
1474
|
[uploadEnabled]
|
|
1100
1475
|
);
|
|
1101
|
-
const onDragLeave =
|
|
1476
|
+
const onDragLeave = React11.useCallback((event) => {
|
|
1102
1477
|
if (!event.dataTransfer?.types?.includes("Files")) return;
|
|
1103
1478
|
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
1104
1479
|
if (dragDepth.current === 0) setDragging(false);
|
|
1105
1480
|
}, []);
|
|
1106
|
-
const onDrop =
|
|
1481
|
+
const onDrop = React11.useCallback(
|
|
1107
1482
|
(event) => {
|
|
1108
1483
|
if (!uploadEnabled) return;
|
|
1109
1484
|
event.preventDefault();
|
|
@@ -1114,7 +1489,7 @@ function AgentPanel({
|
|
|
1114
1489
|
},
|
|
1115
1490
|
[addFiles, uploadEnabled]
|
|
1116
1491
|
);
|
|
1117
|
-
const openHistory =
|
|
1492
|
+
const openHistory = React11.useCallback(() => {
|
|
1118
1493
|
setView("history");
|
|
1119
1494
|
void chat.refreshConversations();
|
|
1120
1495
|
}, [chat]);
|
|
@@ -1137,8 +1512,8 @@ function AgentPanel({
|
|
|
1137
1512
|
children: [
|
|
1138
1513
|
dragging && view === "chat" ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center gap-2 bg-background/85 backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 rounded-2xl border-2 border-primary/40 border-dashed px-8 py-6 text-center", children: [
|
|
1139
1514
|
/* @__PURE__ */ jsx(UploadCloudIcon, { className: "size-8 text-primary" }),
|
|
1140
|
-
/* @__PURE__ */ jsx("div", { className: "font-medium text-sm", children: "Drop
|
|
1141
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-xs", children: acceptAttribute(config).replaceAll(",", " \xB7 ") })
|
|
1515
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-sm", children: "Drop files to attach" }),
|
|
1516
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-xs", children: acceptAttribute(config, caps).replaceAll(",", " \xB7 ") })
|
|
1142
1517
|
] }) }) : null,
|
|
1143
1518
|
/* @__PURE__ */ jsxs(ChatHeader, { children: [
|
|
1144
1519
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2.5", children: [
|
|
@@ -1269,7 +1644,7 @@ function AgentPanel({
|
|
|
1269
1644
|
const anchorKey = newMessageAnchorId ? grouped.find(
|
|
1270
1645
|
(it) => it.kind !== "tool-activity" && it.message.id === newMessageAnchorId
|
|
1271
1646
|
)?.key : void 0;
|
|
1272
|
-
return grouped.map((item) => /* @__PURE__ */ jsxs(
|
|
1647
|
+
return grouped.map((item) => /* @__PURE__ */ jsxs(React11.Fragment, { children: [
|
|
1273
1648
|
item.key === anchorKey ? /* @__PURE__ */ jsx(NewMessagesDivider, {}) : null,
|
|
1274
1649
|
/* @__PURE__ */ jsx(
|
|
1275
1650
|
motion.div,
|
|
@@ -1319,17 +1694,21 @@ function AgentPanel({
|
|
|
1319
1694
|
/* @__PURE__ */ jsx(
|
|
1320
1695
|
AgentComposer,
|
|
1321
1696
|
{
|
|
1322
|
-
acceptAttribute: acceptAttribute(config),
|
|
1697
|
+
acceptAttribute: acceptAttribute(config, caps),
|
|
1323
1698
|
busy,
|
|
1324
1699
|
className: cn(agentMode && "mx-auto w-full max-w-3xl"),
|
|
1325
1700
|
disabled: chat.pendingApprovals.length > 0,
|
|
1326
1701
|
files,
|
|
1702
|
+
onError: setUploadError,
|
|
1327
1703
|
onPickFiles: uploadEnabled ? addFiles : void 0,
|
|
1328
1704
|
onRemoveFile: removeFile,
|
|
1329
1705
|
onSubmit: handleSubmit,
|
|
1706
|
+
onTranscribe: chat.transcribe,
|
|
1330
1707
|
onValueChange: setDraft,
|
|
1331
1708
|
placeholder: chat.pendingApprovals.length > 0 ? "Approve or decline the pending action first\u2026" : `Ask ${agentName}\u2026`,
|
|
1332
|
-
value: draft
|
|
1709
|
+
value: draft,
|
|
1710
|
+
voiceEnabled: chat.agentInfo?.voice_enabled,
|
|
1711
|
+
voiceMaxSeconds: chat.agentInfo?.voice_max_seconds
|
|
1333
1712
|
}
|
|
1334
1713
|
)
|
|
1335
1714
|
] })
|
|
@@ -1365,16 +1744,16 @@ function buildOptimisticUserMessage(text, files) {
|
|
|
1365
1744
|
};
|
|
1366
1745
|
}
|
|
1367
1746
|
function useAgentChat(config) {
|
|
1368
|
-
const client =
|
|
1369
|
-
const [agentInfo, setAgentInfo] =
|
|
1370
|
-
const [conversationId, setConversationId] =
|
|
1371
|
-
const [conversations, setConversations] =
|
|
1372
|
-
const [messages, setMessages] =
|
|
1373
|
-
const [pendingApprovals, setPendingApprovals] =
|
|
1374
|
-
const [sending, setSending] =
|
|
1375
|
-
const [resolvingApprovalId, setResolvingApprovalId] =
|
|
1376
|
-
const [error, setError] =
|
|
1377
|
-
|
|
1747
|
+
const client = React11.useMemo(() => new AgentApiClient(config), [config]);
|
|
1748
|
+
const [agentInfo, setAgentInfo] = React11.useState(null);
|
|
1749
|
+
const [conversationId, setConversationId] = React11.useState(null);
|
|
1750
|
+
const [conversations, setConversations] = React11.useState([]);
|
|
1751
|
+
const [messages, setMessages] = React11.useState([]);
|
|
1752
|
+
const [pendingApprovals, setPendingApprovals] = React11.useState([]);
|
|
1753
|
+
const [sending, setSending] = React11.useState(false);
|
|
1754
|
+
const [resolvingApprovalId, setResolvingApprovalId] = React11.useState(null);
|
|
1755
|
+
const [error, setError] = React11.useState(null);
|
|
1756
|
+
React11.useEffect(() => {
|
|
1378
1757
|
let cancelled = false;
|
|
1379
1758
|
client.getConfig().then((info) => {
|
|
1380
1759
|
if (!cancelled) setAgentInfo(info);
|
|
@@ -1384,11 +1763,11 @@ function useAgentChat(config) {
|
|
|
1384
1763
|
cancelled = true;
|
|
1385
1764
|
};
|
|
1386
1765
|
}, [client]);
|
|
1387
|
-
const handleFailure =
|
|
1766
|
+
const handleFailure = React11.useCallback((err) => {
|
|
1388
1767
|
const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
|
|
1389
1768
|
setError(message);
|
|
1390
1769
|
}, []);
|
|
1391
|
-
const applyResponse =
|
|
1770
|
+
const applyResponse = React11.useCallback(
|
|
1392
1771
|
(response) => {
|
|
1393
1772
|
if (response.status === "error" && !response.messages?.length) {
|
|
1394
1773
|
setError(response.message ?? "The assistant could not answer.");
|
|
@@ -1410,7 +1789,7 @@ function useAgentChat(config) {
|
|
|
1410
1789
|
},
|
|
1411
1790
|
[]
|
|
1412
1791
|
);
|
|
1413
|
-
const handleStreamEvent =
|
|
1792
|
+
const handleStreamEvent = React11.useCallback(
|
|
1414
1793
|
(event, ctx) => {
|
|
1415
1794
|
switch (event.type) {
|
|
1416
1795
|
case "conversation":
|
|
@@ -1475,7 +1854,7 @@ function useAgentChat(config) {
|
|
|
1475
1854
|
[]
|
|
1476
1855
|
);
|
|
1477
1856
|
const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
|
|
1478
|
-
const send =
|
|
1857
|
+
const send = React11.useCallback(
|
|
1479
1858
|
async (text, files) => {
|
|
1480
1859
|
if (sending) return;
|
|
1481
1860
|
if (!text.trim() && !(files && files.length)) return;
|
|
@@ -1506,7 +1885,14 @@ function useAgentChat(config) {
|
|
|
1506
1885
|
},
|
|
1507
1886
|
[applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
|
|
1508
1887
|
);
|
|
1509
|
-
const
|
|
1888
|
+
const transcribe = React11.useCallback(
|
|
1889
|
+
async (audio) => {
|
|
1890
|
+
const { text } = await client.transcribe(audio);
|
|
1891
|
+
return text;
|
|
1892
|
+
},
|
|
1893
|
+
[client]
|
|
1894
|
+
);
|
|
1895
|
+
const resolveApproval = React11.useCallback(
|
|
1510
1896
|
async (callId, approve) => {
|
|
1511
1897
|
if (resolvingApprovalId) return;
|
|
1512
1898
|
setResolvingApprovalId(callId);
|
|
@@ -1535,13 +1921,13 @@ function useAgentChat(config) {
|
|
|
1535
1921
|
},
|
|
1536
1922
|
[applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
|
|
1537
1923
|
);
|
|
1538
|
-
const newChat =
|
|
1924
|
+
const newChat = React11.useCallback(() => {
|
|
1539
1925
|
setConversationId(null);
|
|
1540
1926
|
setMessages([]);
|
|
1541
1927
|
setPendingApprovals([]);
|
|
1542
1928
|
setError(null);
|
|
1543
1929
|
}, []);
|
|
1544
|
-
const refreshConversations =
|
|
1930
|
+
const refreshConversations = React11.useCallback(async () => {
|
|
1545
1931
|
try {
|
|
1546
1932
|
const { conversations: list } = await client.listConversations();
|
|
1547
1933
|
setConversations(list);
|
|
@@ -1551,7 +1937,7 @@ function useAgentChat(config) {
|
|
|
1551
1937
|
return [];
|
|
1552
1938
|
}
|
|
1553
1939
|
}, [client, handleFailure]);
|
|
1554
|
-
const openConversation =
|
|
1940
|
+
const openConversation = React11.useCallback(
|
|
1555
1941
|
async (id) => {
|
|
1556
1942
|
setError(null);
|
|
1557
1943
|
try {
|
|
@@ -1565,7 +1951,7 @@ function useAgentChat(config) {
|
|
|
1565
1951
|
},
|
|
1566
1952
|
[client, handleFailure]
|
|
1567
1953
|
);
|
|
1568
|
-
const deleteConversation =
|
|
1954
|
+
const deleteConversation = React11.useCallback(
|
|
1569
1955
|
async (id) => {
|
|
1570
1956
|
try {
|
|
1571
1957
|
await client.deleteConversation(id);
|
|
@@ -1577,7 +1963,7 @@ function useAgentChat(config) {
|
|
|
1577
1963
|
},
|
|
1578
1964
|
[client, conversationId, handleFailure, newChat]
|
|
1579
1965
|
);
|
|
1580
|
-
const clearError =
|
|
1966
|
+
const clearError = React11.useCallback(() => setError(null), []);
|
|
1581
1967
|
return {
|
|
1582
1968
|
agentInfo,
|
|
1583
1969
|
clearError,
|
|
@@ -1593,7 +1979,8 @@ function useAgentChat(config) {
|
|
|
1593
1979
|
resolveApproval,
|
|
1594
1980
|
resolvingApprovalId,
|
|
1595
1981
|
send,
|
|
1596
|
-
sending
|
|
1982
|
+
sending,
|
|
1983
|
+
transcribe
|
|
1597
1984
|
};
|
|
1598
1985
|
}
|
|
1599
1986
|
var STORAGE_PREFIX = "cc-agent-always-approve:";
|
|
@@ -1612,11 +1999,11 @@ function readStored(clientId) {
|
|
|
1612
1999
|
}
|
|
1613
2000
|
}
|
|
1614
2001
|
function useAlwaysApprove(clientId) {
|
|
1615
|
-
const [approved, setApproved] =
|
|
1616
|
-
|
|
2002
|
+
const [approved, setApproved] = React11.useState(() => /* @__PURE__ */ new Set());
|
|
2003
|
+
React11.useEffect(() => {
|
|
1617
2004
|
setApproved(readStored(clientId));
|
|
1618
2005
|
}, [clientId]);
|
|
1619
|
-
const persist =
|
|
2006
|
+
const persist = React11.useCallback(
|
|
1620
2007
|
(next) => {
|
|
1621
2008
|
setApproved(next);
|
|
1622
2009
|
if (typeof window === "undefined") return;
|
|
@@ -1627,8 +2014,8 @@ function useAlwaysApprove(clientId) {
|
|
|
1627
2014
|
},
|
|
1628
2015
|
[clientId]
|
|
1629
2016
|
);
|
|
1630
|
-
const isAlwaysApproved =
|
|
1631
|
-
const setAlwaysApprove =
|
|
2017
|
+
const isAlwaysApproved = React11.useCallback((toolName) => approved.has(toolName), [approved]);
|
|
2018
|
+
const setAlwaysApprove = React11.useCallback(
|
|
1632
2019
|
(toolName) => {
|
|
1633
2020
|
if (approved.has(toolName)) return;
|
|
1634
2021
|
const next = new Set(approved);
|
|
@@ -1637,7 +2024,7 @@ function useAlwaysApprove(clientId) {
|
|
|
1637
2024
|
},
|
|
1638
2025
|
[approved, persist]
|
|
1639
2026
|
);
|
|
1640
|
-
const clearAlwaysApprove =
|
|
2027
|
+
const clearAlwaysApprove = React11.useCallback(
|
|
1641
2028
|
(toolName) => {
|
|
1642
2029
|
if (!approved.has(toolName)) return;
|
|
1643
2030
|
const next = new Set(approved);
|
|
@@ -1657,9 +2044,10 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1657
2044
|
const position = config.appearance?.position ?? "bottom-right";
|
|
1658
2045
|
const isControlled = controlledOpen !== void 0;
|
|
1659
2046
|
const storageKey2 = `cc-agent-open:${config.clientId}`;
|
|
1660
|
-
const [uncontrolledOpen, setUncontrolledOpen] =
|
|
2047
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React11.useState(false);
|
|
1661
2048
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1662
|
-
const
|
|
2049
|
+
const [bootstrapDone, setBootstrapDone] = React11.useState(isControlled);
|
|
2050
|
+
const persist = React11.useCallback(
|
|
1663
2051
|
(next) => {
|
|
1664
2052
|
if (isControlled || !behavior.persistOpenState) return;
|
|
1665
2053
|
try {
|
|
@@ -1669,7 +2057,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1669
2057
|
},
|
|
1670
2058
|
[behavior.persistOpenState, isControlled, storageKey2]
|
|
1671
2059
|
);
|
|
1672
|
-
const setOpen =
|
|
2060
|
+
const setOpen = React11.useCallback(
|
|
1673
2061
|
(next) => {
|
|
1674
2062
|
setUncontrolledOpen(next);
|
|
1675
2063
|
persist(next);
|
|
@@ -1677,14 +2065,14 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1677
2065
|
},
|
|
1678
2066
|
[onOpenChange, persist]
|
|
1679
2067
|
);
|
|
1680
|
-
const [agentMode, setAgentMode] =
|
|
2068
|
+
const [agentMode, setAgentMode] = React11.useState(false);
|
|
1681
2069
|
const isDesktop = useMediaQuery("(min-width: 640px)", { defaultSSRValue: true, ssr: true });
|
|
1682
2070
|
const fullscreen = !isDesktop || agentMode;
|
|
1683
2071
|
const chat = useAgentChat(config);
|
|
1684
2072
|
const alwaysApprove = useAlwaysApprove(config.clientId);
|
|
1685
2073
|
const { messages, pendingApprovals, resolveApproval, resolvingApprovalId, sending } = chat;
|
|
1686
|
-
const bootRef =
|
|
1687
|
-
|
|
2074
|
+
const bootRef = React11.useRef(false);
|
|
2075
|
+
React11.useEffect(() => {
|
|
1688
2076
|
if (isControlled || bootRef.current) return;
|
|
1689
2077
|
bootRef.current = true;
|
|
1690
2078
|
let cancelled = false;
|
|
@@ -1707,14 +2095,21 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1707
2095
|
setUncontrolledOpen(true);
|
|
1708
2096
|
onOpenChange?.(true);
|
|
1709
2097
|
}
|
|
2098
|
+
setBootstrapDone(true);
|
|
1710
2099
|
})();
|
|
1711
2100
|
return () => {
|
|
1712
2101
|
cancelled = true;
|
|
1713
2102
|
};
|
|
1714
2103
|
}, []);
|
|
1715
|
-
const seenCountRef =
|
|
1716
|
-
const [
|
|
1717
|
-
|
|
2104
|
+
const seenCountRef = React11.useRef(0);
|
|
2105
|
+
const [baselined, setBaselined] = React11.useState(false);
|
|
2106
|
+
const [anchorId, setAnchorId] = React11.useState(null);
|
|
2107
|
+
React11.useEffect(() => {
|
|
2108
|
+
if (baselined || !bootstrapDone) return;
|
|
2109
|
+
seenCountRef.current = messages.length;
|
|
2110
|
+
setBaselined(true);
|
|
2111
|
+
}, [baselined, bootstrapDone, messages.length]);
|
|
2112
|
+
React11.useEffect(() => {
|
|
1718
2113
|
if (open) {
|
|
1719
2114
|
if (messages.length > seenCountRef.current) {
|
|
1720
2115
|
const firstUnread = messages[seenCountRef.current];
|
|
@@ -1723,8 +2118,8 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1723
2118
|
seenCountRef.current = messages.length;
|
|
1724
2119
|
}
|
|
1725
2120
|
}, [open, messages]);
|
|
1726
|
-
const unreadCount = open ? 0 : messages.slice(seenCountRef.current).filter(isUnreadable).length;
|
|
1727
|
-
const workLabel =
|
|
2121
|
+
const unreadCount = open || !baselined ? 0 : messages.slice(seenCountRef.current).filter(isUnreadable).length;
|
|
2122
|
+
const workLabel = React11.useMemo(() => {
|
|
1728
2123
|
if (!sending) return null;
|
|
1729
2124
|
const last = messages[messages.length - 1];
|
|
1730
2125
|
if (last?.role === "tool") return "Calling tools\u2026";
|
|
@@ -1733,14 +2128,14 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1733
2128
|
}, [sending, messages]);
|
|
1734
2129
|
const approvalWaiting = pendingApprovals.length > 0;
|
|
1735
2130
|
const { isAlwaysApproved } = alwaysApprove;
|
|
1736
|
-
|
|
2131
|
+
React11.useEffect(() => {
|
|
1737
2132
|
if (resolvingApprovalId) return;
|
|
1738
2133
|
const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
|
|
1739
2134
|
if (target) void resolveApproval(target.id, true);
|
|
1740
2135
|
}, [pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
|
|
1741
|
-
const firedWriteIds =
|
|
2136
|
+
const firedWriteIds = React11.useRef(/* @__PURE__ */ new Set());
|
|
1742
2137
|
const { onWriteSuccess } = config;
|
|
1743
|
-
|
|
2138
|
+
React11.useEffect(() => {
|
|
1744
2139
|
if (!onWriteSuccess) return;
|
|
1745
2140
|
for (const message of messages) {
|
|
1746
2141
|
if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
|
|
@@ -1750,8 +2145,8 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1750
2145
|
onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
|
|
1751
2146
|
}
|
|
1752
2147
|
}, [messages, onWriteSuccess]);
|
|
1753
|
-
const lastNavigatedMessageId =
|
|
1754
|
-
|
|
2148
|
+
const lastNavigatedMessageId = React11.useRef(null);
|
|
2149
|
+
React11.useEffect(() => {
|
|
1755
2150
|
if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
|
|
1756
2151
|
const toolMessages = messages.filter((message) => message.role === "tool");
|
|
1757
2152
|
const latest = toolMessages[toolMessages.length - 1];
|
|
@@ -1763,7 +2158,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1763
2158
|
const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
|
|
1764
2159
|
if (path) config.navigate(path);
|
|
1765
2160
|
}, [config, fullscreen, messages, open]);
|
|
1766
|
-
const closePanel =
|
|
2161
|
+
const closePanel = React11.useCallback(() => {
|
|
1767
2162
|
setOpen(false);
|
|
1768
2163
|
setAgentMode(false);
|
|
1769
2164
|
}, [setOpen]);
|
|
@@ -1802,7 +2197,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1802
2197
|
animate: { scale: 1 },
|
|
1803
2198
|
exit: { scale: 0 },
|
|
1804
2199
|
transition: { type: "spring", stiffness: 500, damping: 20 },
|
|
1805
|
-
className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-
|
|
2200
|
+
className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1 text-xs font-semibold text-white shadow-md ring-2 ring-background",
|
|
1806
2201
|
"aria-label": `${unreadCount} new message${unreadCount === 1 ? "" : "s"}`,
|
|
1807
2202
|
children: unreadCount > 9 ? "9+" : unreadCount
|
|
1808
2203
|
},
|
|
@@ -1910,6 +2305,6 @@ function defineAgentConfig(config) {
|
|
|
1910
2305
|
return config;
|
|
1911
2306
|
}
|
|
1912
2307
|
|
|
1913
|
-
export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
|
|
2308
|
+
export { AgentApiClient, AgentApiError, AgentComposer, AgentImageLightbox, AgentPanel, AgentVoiceRecorder, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, IMAGE_ACCEPT, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, attachmentsEnabled, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, isImageFile, resolveToolPresentation, textExtensions, useAgentChat, useAlwaysApprove, validateFile };
|
|
1914
2309
|
//# sourceMappingURL=index.js.map
|
|
1915
2310
|
//# sourceMappingURL=index.js.map
|