@cntyclub/agent-react 0.10.2 → 0.12.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/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 +638 -170
- 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 +1 -1
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,386 @@ 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
|
+
var BAR_COUNT = 40;
|
|
316
|
+
function formatClock(totalSeconds) {
|
|
317
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
318
|
+
const seconds = totalSeconds % 60;
|
|
319
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
320
|
+
}
|
|
321
|
+
function pickMimeType() {
|
|
322
|
+
if (typeof MediaRecorder === "undefined") return "";
|
|
323
|
+
for (const type of ["audio/webm", "audio/mp4", "audio/ogg"]) {
|
|
324
|
+
if (MediaRecorder.isTypeSupported(type)) return type;
|
|
325
|
+
}
|
|
326
|
+
return "";
|
|
327
|
+
}
|
|
328
|
+
function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError }) {
|
|
329
|
+
const [phase, setPhase] = React11.useState("recording");
|
|
330
|
+
const [elapsed, setElapsed] = React11.useState(0);
|
|
331
|
+
const [playing, setPlaying] = React11.useState(false);
|
|
332
|
+
const recorderRef = React11.useRef(null);
|
|
333
|
+
const streamRef = React11.useRef(null);
|
|
334
|
+
const chunksRef = React11.useRef([]);
|
|
335
|
+
const blobRef = React11.useRef(null);
|
|
336
|
+
const audioUrlRef = React11.useRef(null);
|
|
337
|
+
const audioRef = React11.useRef(null);
|
|
338
|
+
const timerRef = React11.useRef(null);
|
|
339
|
+
const canvasRef = React11.useRef(null);
|
|
340
|
+
const audioContextRef = React11.useRef(null);
|
|
341
|
+
const analyserRef = React11.useRef(null);
|
|
342
|
+
const rafRef = React11.useRef(null);
|
|
343
|
+
const levelsRef = React11.useRef(new Array(BAR_COUNT).fill(0));
|
|
344
|
+
const onErrorRef = React11.useRef(onError);
|
|
345
|
+
onErrorRef.current = onError;
|
|
346
|
+
const stopMeter = React11.useCallback(() => {
|
|
347
|
+
if (rafRef.current != null) {
|
|
348
|
+
cancelAnimationFrame(rafRef.current);
|
|
349
|
+
rafRef.current = null;
|
|
350
|
+
}
|
|
351
|
+
analyserRef.current = null;
|
|
352
|
+
const ctx = audioContextRef.current;
|
|
353
|
+
audioContextRef.current = null;
|
|
354
|
+
if (ctx && ctx.state !== "closed") void ctx.close().catch(() => {
|
|
355
|
+
});
|
|
356
|
+
}, []);
|
|
357
|
+
const stopTracks = React11.useCallback(() => {
|
|
358
|
+
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
359
|
+
streamRef.current = null;
|
|
360
|
+
if (timerRef.current) {
|
|
361
|
+
clearInterval(timerRef.current);
|
|
362
|
+
timerRef.current = null;
|
|
363
|
+
}
|
|
364
|
+
stopMeter();
|
|
365
|
+
}, [stopMeter]);
|
|
366
|
+
const startMeter = React11.useCallback((stream) => {
|
|
367
|
+
const Ctor = typeof window === "undefined" ? void 0 : window.AudioContext ?? window.webkitAudioContext;
|
|
368
|
+
if (!Ctor) return;
|
|
369
|
+
let context;
|
|
370
|
+
try {
|
|
371
|
+
context = new Ctor();
|
|
372
|
+
} catch {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
audioContextRef.current = context;
|
|
376
|
+
const source = context.createMediaStreamSource(stream);
|
|
377
|
+
const analyser = context.createAnalyser();
|
|
378
|
+
analyser.fftSize = 1024;
|
|
379
|
+
analyser.smoothingTimeConstant = 0.75;
|
|
380
|
+
source.connect(analyser);
|
|
381
|
+
analyserRef.current = analyser;
|
|
382
|
+
levelsRef.current = new Array(BAR_COUNT).fill(0);
|
|
383
|
+
const buffer = new Uint8Array(analyser.fftSize);
|
|
384
|
+
const draw = () => {
|
|
385
|
+
const canvas = canvasRef.current;
|
|
386
|
+
const activeAnalyser = analyserRef.current;
|
|
387
|
+
if (!canvas || !activeAnalyser) return;
|
|
388
|
+
const ctx2d = canvas.getContext("2d");
|
|
389
|
+
if (!ctx2d) return;
|
|
390
|
+
activeAnalyser.getByteTimeDomainData(buffer);
|
|
391
|
+
let sumSquares = 0;
|
|
392
|
+
for (let i = 0; i < buffer.length; i += 1) {
|
|
393
|
+
const centered = (buffer[i] - 128) / 128;
|
|
394
|
+
sumSquares += centered * centered;
|
|
395
|
+
}
|
|
396
|
+
const rms = Math.sqrt(sumSquares / buffer.length);
|
|
397
|
+
const level = Math.min(1, rms * 3.2);
|
|
398
|
+
const levels = levelsRef.current;
|
|
399
|
+
levels.push(level);
|
|
400
|
+
levels.shift();
|
|
401
|
+
const dpr = Math.min(2, typeof window === "undefined" ? 1 : window.devicePixelRatio || 1);
|
|
402
|
+
const cssWidth = canvas.clientWidth || 1;
|
|
403
|
+
const cssHeight = canvas.clientHeight || 1;
|
|
404
|
+
const pxWidth = Math.round(cssWidth * dpr);
|
|
405
|
+
const pxHeight = Math.round(cssHeight * dpr);
|
|
406
|
+
if (canvas.width !== pxWidth) canvas.width = pxWidth;
|
|
407
|
+
if (canvas.height !== pxHeight) canvas.height = pxHeight;
|
|
408
|
+
ctx2d.clearRect(0, 0, pxWidth, pxHeight);
|
|
409
|
+
ctx2d.fillStyle = getComputedStyle(canvas).color || "currentColor";
|
|
410
|
+
const gap = Math.max(1, pxWidth / BAR_COUNT / 3);
|
|
411
|
+
const barWidth = Math.max(1, (pxWidth - gap * (BAR_COUNT - 1)) / BAR_COUNT);
|
|
412
|
+
const midY = pxHeight / 2;
|
|
413
|
+
const minBar = Math.max(1, dpr);
|
|
414
|
+
const radius = barWidth / 2;
|
|
415
|
+
for (let i = 0; i < BAR_COUNT; i += 1) {
|
|
416
|
+
const value = levels[i];
|
|
417
|
+
const barHeight = Math.max(minBar, value * pxHeight);
|
|
418
|
+
const x = i * (barWidth + gap);
|
|
419
|
+
const y = midY - barHeight / 2;
|
|
420
|
+
if (typeof ctx2d.roundRect === "function") {
|
|
421
|
+
ctx2d.beginPath();
|
|
422
|
+
ctx2d.roundRect(x, y, barWidth, barHeight, Math.min(radius, barHeight / 2));
|
|
423
|
+
ctx2d.fill();
|
|
424
|
+
} else {
|
|
425
|
+
ctx2d.fillRect(x, y, barWidth, barHeight);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
rafRef.current = requestAnimationFrame(draw);
|
|
429
|
+
};
|
|
430
|
+
rafRef.current = requestAnimationFrame(draw);
|
|
431
|
+
}, []);
|
|
432
|
+
React11.useEffect(() => {
|
|
433
|
+
let cancelled = false;
|
|
434
|
+
void (async () => {
|
|
435
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
436
|
+
onErrorRef.current("Voice recording isn't supported in this browser.");
|
|
437
|
+
onCancel();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
442
|
+
if (cancelled) {
|
|
443
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
streamRef.current = stream;
|
|
447
|
+
startMeter(stream);
|
|
448
|
+
const mimeType = pickMimeType();
|
|
449
|
+
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
|
|
450
|
+
recorderRef.current = recorder;
|
|
451
|
+
chunksRef.current = [];
|
|
452
|
+
recorder.ondataavailable = (event) => {
|
|
453
|
+
if (event.data.size > 0) chunksRef.current.push(event.data);
|
|
454
|
+
};
|
|
455
|
+
recorder.onstop = () => {
|
|
456
|
+
const blob = new Blob(chunksRef.current, { type: mimeType || "audio/webm" });
|
|
457
|
+
blobRef.current = blob;
|
|
458
|
+
if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
|
|
459
|
+
audioUrlRef.current = URL.createObjectURL(blob);
|
|
460
|
+
setPhase("review");
|
|
461
|
+
};
|
|
462
|
+
recorder.start();
|
|
463
|
+
timerRef.current = setInterval(() => {
|
|
464
|
+
setElapsed((current) => {
|
|
465
|
+
const next = current + 1;
|
|
466
|
+
if (next >= maxSeconds) stopRecording();
|
|
467
|
+
return next;
|
|
468
|
+
});
|
|
469
|
+
}, 1e3);
|
|
470
|
+
} catch {
|
|
471
|
+
onErrorRef.current("Microphone access was blocked. Enable it to record.");
|
|
472
|
+
onCancel();
|
|
473
|
+
}
|
|
474
|
+
})();
|
|
475
|
+
return () => {
|
|
476
|
+
cancelled = true;
|
|
477
|
+
stopTracks();
|
|
478
|
+
if (recorderRef.current && recorderRef.current.state !== "inactive") {
|
|
479
|
+
try {
|
|
480
|
+
recorderRef.current.stop();
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
|
|
485
|
+
};
|
|
486
|
+
}, []);
|
|
487
|
+
function stopRecording() {
|
|
488
|
+
if (timerRef.current) {
|
|
489
|
+
clearInterval(timerRef.current);
|
|
490
|
+
timerRef.current = null;
|
|
491
|
+
}
|
|
492
|
+
const recorder = recorderRef.current;
|
|
493
|
+
if (recorder && recorder.state !== "inactive") recorder.stop();
|
|
494
|
+
stopTracks();
|
|
495
|
+
}
|
|
496
|
+
const togglePlayback = () => {
|
|
497
|
+
const audio = audioRef.current;
|
|
498
|
+
if (!audio) return;
|
|
499
|
+
if (audio.paused) {
|
|
500
|
+
void audio.play();
|
|
501
|
+
setPlaying(true);
|
|
502
|
+
} else {
|
|
503
|
+
audio.pause();
|
|
504
|
+
setPlaying(false);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const confirm = async () => {
|
|
508
|
+
const blob = blobRef.current;
|
|
509
|
+
if (!blob || blob.size === 0) {
|
|
510
|
+
onError("The recording was empty.");
|
|
511
|
+
onCancel();
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
setPhase("transcribing");
|
|
515
|
+
try {
|
|
516
|
+
const text = await transcribe(blob);
|
|
517
|
+
onDone(text.trim());
|
|
518
|
+
} catch {
|
|
519
|
+
onError("Transcription failed. Please try again.");
|
|
520
|
+
setPhase("review");
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
return /* @__PURE__ */ jsxs(
|
|
524
|
+
motion.div,
|
|
525
|
+
{
|
|
526
|
+
animate: { opacity: 1, y: 0 },
|
|
527
|
+
className: "flex min-h-8 flex-1 items-center gap-2 px-1",
|
|
528
|
+
initial: { opacity: 0, y: 4 },
|
|
529
|
+
transition: { duration: 0.15 },
|
|
530
|
+
children: [
|
|
531
|
+
phase === "recording" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
532
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2 text-sm", children: [
|
|
533
|
+
/* @__PURE__ */ jsx(
|
|
534
|
+
motion.span,
|
|
535
|
+
{
|
|
536
|
+
animate: { opacity: [1, 0.35, 1] },
|
|
537
|
+
className: "size-2.5 rounded-full bg-destructive",
|
|
538
|
+
transition: { duration: 1.2, repeat: Infinity }
|
|
539
|
+
}
|
|
540
|
+
),
|
|
541
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums", children: formatClock(elapsed) })
|
|
542
|
+
] }),
|
|
543
|
+
/* @__PURE__ */ jsx("canvas", { "aria-hidden": true, className: "h-6 min-w-0 flex-1 text-foreground/80", ref: canvasRef }),
|
|
544
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Discard recording", onClick: onCancel, size: "icon-sm", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
|
|
545
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Stop recording", onClick: stopRecording, size: "icon-sm", type: "button", children: /* @__PURE__ */ jsx(SquareIcon, {}) })
|
|
546
|
+
] }) : null,
|
|
547
|
+
phase === "review" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
548
|
+
/* @__PURE__ */ jsx("audio", { onEnded: () => setPlaying(false), ref: audioRef, src: audioUrlRef.current ?? void 0 }),
|
|
549
|
+
/* @__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, {}) }),
|
|
550
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm tabular-nums", children: formatClock(elapsed) }),
|
|
551
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
552
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Discard recording", onClick: onCancel, size: "icon-sm", type: "button", variant: "ghost", children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
|
|
553
|
+
/* @__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, {}) })
|
|
554
|
+
] }) : null,
|
|
555
|
+
phase === "transcribing" ? /* @__PURE__ */ jsxs("span", { className: cn("flex flex-1 items-center gap-2 text-muted-foreground text-sm"), children: [
|
|
556
|
+
/* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" }),
|
|
557
|
+
"Transcribing\u2026"
|
|
558
|
+
] }) : null
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
function appendText(current, addition) {
|
|
564
|
+
if (!addition) return current;
|
|
565
|
+
if (!current) return addition;
|
|
566
|
+
return /\s$/.test(current) ? current + addition : `${current} ${addition}`;
|
|
567
|
+
}
|
|
221
568
|
function AgentComposer({
|
|
222
569
|
acceptAttribute: acceptAttribute2,
|
|
223
570
|
busy,
|
|
224
571
|
className,
|
|
225
572
|
disabled,
|
|
226
573
|
files,
|
|
574
|
+
onError,
|
|
227
575
|
onPickFiles,
|
|
228
576
|
onRemoveFile,
|
|
229
577
|
onSubmit,
|
|
578
|
+
onTranscribe,
|
|
230
579
|
onValueChange,
|
|
231
580
|
placeholder = "Ask anything\u2026",
|
|
232
|
-
value
|
|
581
|
+
value,
|
|
582
|
+
voiceEnabled,
|
|
583
|
+
voiceMaxSeconds = 600
|
|
233
584
|
}) {
|
|
234
|
-
const textareaRef =
|
|
235
|
-
const inputRef =
|
|
585
|
+
const textareaRef = React11.useRef(null);
|
|
586
|
+
const inputRef = React11.useRef(null);
|
|
587
|
+
const [recording, setRecording] = React11.useState(false);
|
|
588
|
+
const [lightboxSrc, setLightboxSrc] = React11.useState(null);
|
|
236
589
|
const canSubmit = !disabled && !busy && (value.trim().length > 0 || files.length > 0);
|
|
237
|
-
const
|
|
590
|
+
const voiceAvailable = Boolean(voiceEnabled && onTranscribe);
|
|
591
|
+
const submit = React11.useCallback(() => {
|
|
238
592
|
if (!canSubmit) return;
|
|
239
593
|
onSubmit(value.trim());
|
|
240
594
|
textareaRef.current?.focus();
|
|
241
595
|
}, [canSubmit, onSubmit, value]);
|
|
242
|
-
|
|
596
|
+
React11.useEffect(() => {
|
|
243
597
|
if (!disabled) textareaRef.current?.focus();
|
|
244
598
|
}, [disabled]);
|
|
245
|
-
|
|
599
|
+
React11.useEffect(() => {
|
|
246
600
|
const textarea = textareaRef.current;
|
|
247
601
|
if (!textarea) return;
|
|
248
602
|
textarea.style.height = "auto";
|
|
249
603
|
textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`;
|
|
250
604
|
}, [value]);
|
|
605
|
+
const previews = React11.useMemo(
|
|
606
|
+
() => files.map((file, originalIndex) => {
|
|
607
|
+
const image = isImageFile(file);
|
|
608
|
+
return { file, image, originalIndex, url: image ? URL.createObjectURL(file) : null };
|
|
609
|
+
}),
|
|
610
|
+
[files]
|
|
611
|
+
);
|
|
612
|
+
React11.useEffect(
|
|
613
|
+
() => () => {
|
|
614
|
+
previews.forEach((preview) => preview.url && URL.revokeObjectURL(preview.url));
|
|
615
|
+
},
|
|
616
|
+
[previews]
|
|
617
|
+
);
|
|
618
|
+
const ordered = React11.useMemo(
|
|
619
|
+
() => [...previews].sort((a, b) => Number(b.image) - Number(a.image)),
|
|
620
|
+
[previews]
|
|
621
|
+
);
|
|
251
622
|
const handlePick = (event) => {
|
|
252
623
|
const picked = Array.from(event.target.files ?? []);
|
|
253
624
|
if (picked.length) onPickFiles?.(picked);
|
|
254
625
|
event.target.value = "";
|
|
255
626
|
};
|
|
256
|
-
|
|
627
|
+
const handlePaste = (event) => {
|
|
628
|
+
if (!onPickFiles) return;
|
|
629
|
+
const images = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
|
|
630
|
+
if (images.length) {
|
|
631
|
+
event.preventDefault();
|
|
632
|
+
onPickFiles(images);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
const finishRecording = (text) => {
|
|
636
|
+
setRecording(false);
|
|
637
|
+
if (text) onValueChange(appendText(value, text));
|
|
638
|
+
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
639
|
+
};
|
|
640
|
+
return /* @__PURE__ */ jsxs(
|
|
257
641
|
"form",
|
|
258
642
|
{
|
|
259
643
|
className: cn("shrink-0 bg-background p-3 pt-1.5", className),
|
|
@@ -262,96 +646,148 @@ function AgentComposer({
|
|
|
262
646
|
event.preventDefault();
|
|
263
647
|
submit();
|
|
264
648
|
},
|
|
265
|
-
children:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
649
|
+
children: [
|
|
650
|
+
/* @__PURE__ */ jsxs(
|
|
651
|
+
"div",
|
|
652
|
+
{
|
|
653
|
+
className: cn(
|
|
654
|
+
"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",
|
|
655
|
+
disabled && "opacity-64"
|
|
656
|
+
),
|
|
657
|
+
children: [
|
|
658
|
+
files.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pt-1", children: ordered.map(
|
|
659
|
+
(preview) => preview.image && preview.url ? /* @__PURE__ */ jsxs("span", { className: "group relative", children: [
|
|
660
|
+
/* @__PURE__ */ jsx(
|
|
661
|
+
"button",
|
|
662
|
+
{
|
|
663
|
+
"aria-label": `Preview ${preview.file.name}`,
|
|
664
|
+
className: "block size-14 cursor-zoom-in overflow-hidden rounded-lg border border-border/72",
|
|
665
|
+
onClick: () => setLightboxSrc(preview.url),
|
|
666
|
+
type: "button",
|
|
667
|
+
children: /* @__PURE__ */ jsx("img", { alt: preview.file.name, className: "size-full object-cover", src: preview.url })
|
|
668
|
+
}
|
|
669
|
+
),
|
|
281
670
|
onRemoveFile ? /* @__PURE__ */ jsx(
|
|
282
671
|
"button",
|
|
283
672
|
{
|
|
284
|
-
"aria-label": `Remove ${file.name}`,
|
|
285
|
-
className: "flex size-
|
|
286
|
-
onClick: () => onRemoveFile(
|
|
673
|
+
"aria-label": `Remove ${preview.file.name}`,
|
|
674
|
+
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",
|
|
675
|
+
onClick: () => onRemoveFile(preview.originalIndex),
|
|
287
676
|
type: "button",
|
|
288
677
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
289
678
|
}
|
|
290
679
|
) : null
|
|
291
|
-
]
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
680
|
+
] }, `${preview.file.name}-${preview.originalIndex}`) : /* @__PURE__ */ jsxs(
|
|
681
|
+
"span",
|
|
682
|
+
{
|
|
683
|
+
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",
|
|
684
|
+
children: [
|
|
685
|
+
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
|
|
686
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: preview.file.name }),
|
|
687
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatBytes(preview.file.size) }),
|
|
688
|
+
onRemoveFile ? /* @__PURE__ */ jsx(
|
|
689
|
+
"button",
|
|
690
|
+
{
|
|
691
|
+
"aria-label": `Remove ${preview.file.name}`,
|
|
692
|
+
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",
|
|
693
|
+
onClick: () => onRemoveFile(preview.originalIndex),
|
|
694
|
+
type: "button",
|
|
695
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
696
|
+
}
|
|
697
|
+
) : null
|
|
698
|
+
]
|
|
699
|
+
},
|
|
700
|
+
`${preview.file.name}-${preview.originalIndex}`
|
|
701
|
+
)
|
|
702
|
+
) }) : null,
|
|
703
|
+
recording && onTranscribe ? /* @__PURE__ */ jsx(
|
|
704
|
+
AgentVoiceRecorder,
|
|
705
|
+
{
|
|
706
|
+
maxSeconds: voiceMaxSeconds,
|
|
707
|
+
onCancel: () => {
|
|
708
|
+
setRecording(false);
|
|
709
|
+
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
710
|
+
},
|
|
711
|
+
onDone: finishRecording,
|
|
712
|
+
onError: (message) => onError?.(message),
|
|
713
|
+
transcribe: onTranscribe
|
|
714
|
+
}
|
|
715
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5", children: [
|
|
716
|
+
onPickFiles ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
717
|
+
/* @__PURE__ */ jsx(
|
|
718
|
+
"input",
|
|
719
|
+
{
|
|
720
|
+
accept: acceptAttribute2,
|
|
721
|
+
className: "hidden",
|
|
722
|
+
multiple: true,
|
|
723
|
+
onChange: handlePick,
|
|
724
|
+
ref: inputRef,
|
|
725
|
+
type: "file"
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsx(
|
|
729
|
+
Button,
|
|
730
|
+
{
|
|
731
|
+
"aria-label": "Attach files",
|
|
732
|
+
className: "shrink-0 self-center",
|
|
733
|
+
disabled,
|
|
734
|
+
onClick: () => inputRef.current?.click(),
|
|
735
|
+
size: "icon-sm",
|
|
736
|
+
type: "button",
|
|
737
|
+
variant: "ghost",
|
|
738
|
+
children: /* @__PURE__ */ jsx(PaperclipIcon, {})
|
|
739
|
+
}
|
|
740
|
+
)
|
|
741
|
+
] }) : null,
|
|
297
742
|
/* @__PURE__ */ jsx(
|
|
298
|
-
"
|
|
743
|
+
"textarea",
|
|
299
744
|
{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
745
|
+
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",
|
|
746
|
+
disabled,
|
|
747
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
748
|
+
onKeyDown: (event) => {
|
|
749
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
750
|
+
event.preventDefault();
|
|
751
|
+
submit();
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
onPaste: onPickFiles ? handlePaste : void 0,
|
|
755
|
+
placeholder,
|
|
756
|
+
ref: textareaRef,
|
|
757
|
+
rows: 1,
|
|
758
|
+
value
|
|
306
759
|
}
|
|
307
760
|
),
|
|
308
|
-
/* @__PURE__ */ jsx(
|
|
761
|
+
voiceAvailable ? /* @__PURE__ */ jsx(
|
|
309
762
|
Button,
|
|
310
763
|
{
|
|
311
|
-
"aria-label": "
|
|
764
|
+
"aria-label": "Record voice",
|
|
312
765
|
className: "shrink-0 self-center",
|
|
313
766
|
disabled,
|
|
314
|
-
onClick: () =>
|
|
767
|
+
onClick: () => setRecording(true),
|
|
315
768
|
size: "icon-sm",
|
|
316
769
|
type: "button",
|
|
317
770
|
variant: "ghost",
|
|
318
|
-
children: /* @__PURE__ */ jsx(
|
|
771
|
+
children: /* @__PURE__ */ jsx(MicIcon, {})
|
|
772
|
+
}
|
|
773
|
+
) : null,
|
|
774
|
+
/* @__PURE__ */ jsx(
|
|
775
|
+
Button,
|
|
776
|
+
{
|
|
777
|
+
"aria-label": "Send message",
|
|
778
|
+
className: "shrink-0 rounded-full before:rounded-full",
|
|
779
|
+
disabled: !canSubmit,
|
|
780
|
+
size: "icon-sm",
|
|
781
|
+
type: "submit",
|
|
782
|
+
children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
|
|
319
783
|
}
|
|
320
784
|
)
|
|
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
|
-
)
|
|
785
|
+
] })
|
|
786
|
+
]
|
|
787
|
+
}
|
|
788
|
+
),
|
|
789
|
+
/* @__PURE__ */ jsx(AgentImageLightbox, { onClose: () => setLightboxSrc(null), src: lightboxSrc })
|
|
790
|
+
]
|
|
355
791
|
}
|
|
356
792
|
);
|
|
357
793
|
}
|
|
@@ -424,15 +860,15 @@ function groupMessages(messages) {
|
|
|
424
860
|
return items;
|
|
425
861
|
}
|
|
426
862
|
function ChatScroller({ children, className, ...props }) {
|
|
427
|
-
const viewportRef =
|
|
428
|
-
const pinnedRef =
|
|
429
|
-
const handleScroll =
|
|
863
|
+
const viewportRef = React11.useRef(null);
|
|
864
|
+
const pinnedRef = React11.useRef(true);
|
|
865
|
+
const handleScroll = React11.useCallback(() => {
|
|
430
866
|
const viewport = viewportRef.current;
|
|
431
867
|
if (!viewport) return;
|
|
432
868
|
const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
433
869
|
pinnedRef.current = distanceFromBottom < 64;
|
|
434
870
|
}, []);
|
|
435
|
-
|
|
871
|
+
React11.useEffect(() => {
|
|
436
872
|
const viewport = viewportRef.current;
|
|
437
873
|
if (!viewport || !pinnedRef.current) return;
|
|
438
874
|
viewport.scrollTop = viewport.scrollHeight;
|
|
@@ -450,14 +886,14 @@ function ChatScroller({ children, className, ...props }) {
|
|
|
450
886
|
);
|
|
451
887
|
}
|
|
452
888
|
function CopyButton({ className, text }) {
|
|
453
|
-
const [copied, setCopied] =
|
|
454
|
-
const timerRef =
|
|
455
|
-
|
|
889
|
+
const [copied, setCopied] = React11.useState(false);
|
|
890
|
+
const timerRef = React11.useRef(null);
|
|
891
|
+
React11.useEffect(() => {
|
|
456
892
|
return () => {
|
|
457
893
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
458
894
|
};
|
|
459
895
|
}, []);
|
|
460
|
-
const copy =
|
|
896
|
+
const copy = React11.useCallback(async () => {
|
|
461
897
|
try {
|
|
462
898
|
await navigator.clipboard.writeText(text);
|
|
463
899
|
} catch {
|
|
@@ -488,11 +924,26 @@ function CopyButton({ className, text }) {
|
|
|
488
924
|
/* @__PURE__ */ jsx(TooltipContent, { children: copied ? "Copied!" : "Copy" })
|
|
489
925
|
] });
|
|
490
926
|
}
|
|
927
|
+
var isImageAttachment = (format) => IMAGE_ACCEPT.includes(`.${format.toLowerCase()}`);
|
|
491
928
|
function MessageItem({ message }) {
|
|
929
|
+
const [lightboxSrc, setLightboxSrc] = React11.useState(null);
|
|
492
930
|
if (message.role === "user") {
|
|
493
931
|
const attachments = message.attachments ?? [];
|
|
932
|
+
const images = attachments.filter((a) => isImageAttachment(a.format) && a.url);
|
|
933
|
+
const others = attachments.filter((a) => !isImageAttachment(a.format) || !a.url);
|
|
494
934
|
return /* @__PURE__ */ jsxs(ChatMessage, { from: "user", children: [
|
|
495
|
-
|
|
935
|
+
images.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-end gap-1.5", children: images.map((attachment) => /* @__PURE__ */ jsx(
|
|
936
|
+
"button",
|
|
937
|
+
{
|
|
938
|
+
"aria-label": `Preview ${attachment.filename}`,
|
|
939
|
+
className: "block size-20 cursor-zoom-in overflow-hidden rounded-lg border border-border/72",
|
|
940
|
+
onClick: () => setLightboxSrc(attachment.url ?? null),
|
|
941
|
+
type: "button",
|
|
942
|
+
children: /* @__PURE__ */ jsx("img", { alt: attachment.filename, className: "size-full object-cover", src: attachment.url ?? void 0 })
|
|
943
|
+
},
|
|
944
|
+
attachment.id
|
|
945
|
+
)) }) : null,
|
|
946
|
+
others.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-end gap-1.5", children: others.map((attachment) => /* @__PURE__ */ jsxs(
|
|
496
947
|
"span",
|
|
497
948
|
{
|
|
498
949
|
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 +954,8 @@ function MessageItem({ message }) {
|
|
|
503
954
|
},
|
|
504
955
|
attachment.id
|
|
505
956
|
)) }) : null,
|
|
506
|
-
message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) : null
|
|
957
|
+
message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) : null,
|
|
958
|
+
/* @__PURE__ */ jsx(AgentImageLightbox, { onClose: () => setLightboxSrc(null), src: lightboxSrc })
|
|
507
959
|
] });
|
|
508
960
|
}
|
|
509
961
|
if (message.role === "assistant") {
|
|
@@ -534,8 +986,8 @@ var DEFAULT_PHRASES = [
|
|
|
534
986
|
];
|
|
535
987
|
function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
|
|
536
988
|
const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
|
|
537
|
-
const [index, setIndex] =
|
|
538
|
-
|
|
989
|
+
const [index, setIndex] = React11.useState(0);
|
|
990
|
+
React11.useEffect(() => {
|
|
539
991
|
setIndex(0);
|
|
540
992
|
const timer = setInterval(() => {
|
|
541
993
|
setIndex((current) => (current + 1) % words.length);
|
|
@@ -797,7 +1249,7 @@ function DownloadButton({ block, size = "sm" }) {
|
|
|
797
1249
|
);
|
|
798
1250
|
}
|
|
799
1251
|
function DocumentViewer({ block, onClose }) {
|
|
800
|
-
|
|
1252
|
+
React11.useEffect(() => {
|
|
801
1253
|
const onKey = (event) => event.key === "Escape" && onClose();
|
|
802
1254
|
window.addEventListener("keydown", onKey);
|
|
803
1255
|
return () => window.removeEventListener("keydown", onKey);
|
|
@@ -822,7 +1274,7 @@ function DocumentViewer({ block, onClose }) {
|
|
|
822
1274
|
] });
|
|
823
1275
|
}
|
|
824
1276
|
function DocumentCard({ block }) {
|
|
825
|
-
const [open, setOpen] =
|
|
1277
|
+
const [open, setOpen] = React11.useState(false);
|
|
826
1278
|
const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
|
|
827
1279
|
return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
|
|
828
1280
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
|
|
@@ -944,7 +1396,7 @@ function StepDetail({
|
|
|
944
1396
|
] });
|
|
945
1397
|
}
|
|
946
1398
|
function ToolActivityGroup({ describeToolCall, steps }) {
|
|
947
|
-
const [open, setOpen] =
|
|
1399
|
+
const [open, setOpen] = React11.useState(false);
|
|
948
1400
|
if (steps.length === 0) return null;
|
|
949
1401
|
const last = steps[steps.length - 1];
|
|
950
1402
|
const anyRunning = steps.some((step) => step.status === "running");
|
|
@@ -1048,24 +1500,28 @@ function AgentPanel({
|
|
|
1048
1500
|
onClose,
|
|
1049
1501
|
onToggleAgentMode
|
|
1050
1502
|
}) {
|
|
1051
|
-
const [draft, setDraft] =
|
|
1052
|
-
const [view, setView] =
|
|
1053
|
-
const [files, setFiles] =
|
|
1054
|
-
const [uploadError, setUploadError] =
|
|
1055
|
-
const [dragging, setDragging] =
|
|
1056
|
-
const dragDepth =
|
|
1503
|
+
const [draft, setDraft] = React11.useState("");
|
|
1504
|
+
const [view, setView] = React11.useState("chat");
|
|
1505
|
+
const [files, setFiles] = React11.useState([]);
|
|
1506
|
+
const [uploadError, setUploadError] = React11.useState(null);
|
|
1507
|
+
const [dragging, setDragging] = React11.useState(false);
|
|
1508
|
+
const dragDepth = React11.useRef(0);
|
|
1057
1509
|
const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
|
|
1058
|
-
const
|
|
1510
|
+
const caps = {
|
|
1511
|
+
visionEnabled: chat.agentInfo?.vision_enabled,
|
|
1512
|
+
visionMaxMb: chat.agentInfo?.vision_max_file_mb
|
|
1513
|
+
};
|
|
1514
|
+
const uploadEnabled = attachmentsEnabled(config, caps);
|
|
1059
1515
|
const suggestions = config.suggestions ?? [
|
|
1060
1516
|
"What can you help me with?",
|
|
1061
1517
|
"Give me a quick summary of my data."
|
|
1062
1518
|
];
|
|
1063
|
-
const addFiles =
|
|
1519
|
+
const addFiles = React11.useCallback(
|
|
1064
1520
|
(incoming) => {
|
|
1065
1521
|
const accepted = [];
|
|
1066
1522
|
let rejection = null;
|
|
1067
1523
|
for (const file of incoming) {
|
|
1068
|
-
const error = validateFile(config, file);
|
|
1524
|
+
const error = validateFile(config, file, caps);
|
|
1069
1525
|
if (error) {
|
|
1070
1526
|
rejection = error;
|
|
1071
1527
|
continue;
|
|
@@ -1075,12 +1531,12 @@ function AgentPanel({
|
|
|
1075
1531
|
setUploadError(rejection);
|
|
1076
1532
|
if (accepted.length) setFiles((current) => [...current, ...accepted].slice(0, 10));
|
|
1077
1533
|
},
|
|
1078
|
-
[config]
|
|
1534
|
+
[config, caps.visionEnabled, caps.visionMaxMb]
|
|
1079
1535
|
);
|
|
1080
|
-
const removeFile =
|
|
1536
|
+
const removeFile = React11.useCallback((index) => {
|
|
1081
1537
|
setFiles((current) => current.filter((_, i) => i !== index));
|
|
1082
1538
|
}, []);
|
|
1083
|
-
const handleSubmit =
|
|
1539
|
+
const handleSubmit = React11.useCallback(
|
|
1084
1540
|
(text) => {
|
|
1085
1541
|
const staged = files;
|
|
1086
1542
|
setDraft("");
|
|
@@ -1090,7 +1546,7 @@ function AgentPanel({
|
|
|
1090
1546
|
},
|
|
1091
1547
|
[chat, files]
|
|
1092
1548
|
);
|
|
1093
|
-
const onDragEnter =
|
|
1549
|
+
const onDragEnter = React11.useCallback(
|
|
1094
1550
|
(event) => {
|
|
1095
1551
|
if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
|
|
1096
1552
|
dragDepth.current += 1;
|
|
@@ -1098,12 +1554,12 @@ function AgentPanel({
|
|
|
1098
1554
|
},
|
|
1099
1555
|
[uploadEnabled]
|
|
1100
1556
|
);
|
|
1101
|
-
const onDragLeave =
|
|
1557
|
+
const onDragLeave = React11.useCallback((event) => {
|
|
1102
1558
|
if (!event.dataTransfer?.types?.includes("Files")) return;
|
|
1103
1559
|
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
1104
1560
|
if (dragDepth.current === 0) setDragging(false);
|
|
1105
1561
|
}, []);
|
|
1106
|
-
const onDrop =
|
|
1562
|
+
const onDrop = React11.useCallback(
|
|
1107
1563
|
(event) => {
|
|
1108
1564
|
if (!uploadEnabled) return;
|
|
1109
1565
|
event.preventDefault();
|
|
@@ -1114,7 +1570,7 @@ function AgentPanel({
|
|
|
1114
1570
|
},
|
|
1115
1571
|
[addFiles, uploadEnabled]
|
|
1116
1572
|
);
|
|
1117
|
-
const openHistory =
|
|
1573
|
+
const openHistory = React11.useCallback(() => {
|
|
1118
1574
|
setView("history");
|
|
1119
1575
|
void chat.refreshConversations();
|
|
1120
1576
|
}, [chat]);
|
|
@@ -1137,8 +1593,8 @@ function AgentPanel({
|
|
|
1137
1593
|
children: [
|
|
1138
1594
|
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
1595
|
/* @__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 ") })
|
|
1596
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-sm", children: "Drop files to attach" }),
|
|
1597
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-xs", children: acceptAttribute(config, caps).replaceAll(",", " \xB7 ") })
|
|
1142
1598
|
] }) }) : null,
|
|
1143
1599
|
/* @__PURE__ */ jsxs(ChatHeader, { children: [
|
|
1144
1600
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2.5", children: [
|
|
@@ -1269,7 +1725,7 @@ function AgentPanel({
|
|
|
1269
1725
|
const anchorKey = newMessageAnchorId ? grouped.find(
|
|
1270
1726
|
(it) => it.kind !== "tool-activity" && it.message.id === newMessageAnchorId
|
|
1271
1727
|
)?.key : void 0;
|
|
1272
|
-
return grouped.map((item) => /* @__PURE__ */ jsxs(
|
|
1728
|
+
return grouped.map((item) => /* @__PURE__ */ jsxs(React11.Fragment, { children: [
|
|
1273
1729
|
item.key === anchorKey ? /* @__PURE__ */ jsx(NewMessagesDivider, {}) : null,
|
|
1274
1730
|
/* @__PURE__ */ jsx(
|
|
1275
1731
|
motion.div,
|
|
@@ -1319,17 +1775,21 @@ function AgentPanel({
|
|
|
1319
1775
|
/* @__PURE__ */ jsx(
|
|
1320
1776
|
AgentComposer,
|
|
1321
1777
|
{
|
|
1322
|
-
acceptAttribute: acceptAttribute(config),
|
|
1778
|
+
acceptAttribute: acceptAttribute(config, caps),
|
|
1323
1779
|
busy,
|
|
1324
1780
|
className: cn(agentMode && "mx-auto w-full max-w-3xl"),
|
|
1325
1781
|
disabled: chat.pendingApprovals.length > 0,
|
|
1326
1782
|
files,
|
|
1783
|
+
onError: setUploadError,
|
|
1327
1784
|
onPickFiles: uploadEnabled ? addFiles : void 0,
|
|
1328
1785
|
onRemoveFile: removeFile,
|
|
1329
1786
|
onSubmit: handleSubmit,
|
|
1787
|
+
onTranscribe: chat.transcribe,
|
|
1330
1788
|
onValueChange: setDraft,
|
|
1331
1789
|
placeholder: chat.pendingApprovals.length > 0 ? "Approve or decline the pending action first\u2026" : `Ask ${agentName}\u2026`,
|
|
1332
|
-
value: draft
|
|
1790
|
+
value: draft,
|
|
1791
|
+
voiceEnabled: chat.agentInfo?.voice_enabled,
|
|
1792
|
+
voiceMaxSeconds: chat.agentInfo?.voice_max_seconds
|
|
1333
1793
|
}
|
|
1334
1794
|
)
|
|
1335
1795
|
] })
|
|
@@ -1365,16 +1825,16 @@ function buildOptimisticUserMessage(text, files) {
|
|
|
1365
1825
|
};
|
|
1366
1826
|
}
|
|
1367
1827
|
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
|
-
|
|
1828
|
+
const client = React11.useMemo(() => new AgentApiClient(config), [config]);
|
|
1829
|
+
const [agentInfo, setAgentInfo] = React11.useState(null);
|
|
1830
|
+
const [conversationId, setConversationId] = React11.useState(null);
|
|
1831
|
+
const [conversations, setConversations] = React11.useState([]);
|
|
1832
|
+
const [messages, setMessages] = React11.useState([]);
|
|
1833
|
+
const [pendingApprovals, setPendingApprovals] = React11.useState([]);
|
|
1834
|
+
const [sending, setSending] = React11.useState(false);
|
|
1835
|
+
const [resolvingApprovalId, setResolvingApprovalId] = React11.useState(null);
|
|
1836
|
+
const [error, setError] = React11.useState(null);
|
|
1837
|
+
React11.useEffect(() => {
|
|
1378
1838
|
let cancelled = false;
|
|
1379
1839
|
client.getConfig().then((info) => {
|
|
1380
1840
|
if (!cancelled) setAgentInfo(info);
|
|
@@ -1384,11 +1844,11 @@ function useAgentChat(config) {
|
|
|
1384
1844
|
cancelled = true;
|
|
1385
1845
|
};
|
|
1386
1846
|
}, [client]);
|
|
1387
|
-
const handleFailure =
|
|
1847
|
+
const handleFailure = React11.useCallback((err) => {
|
|
1388
1848
|
const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
|
|
1389
1849
|
setError(message);
|
|
1390
1850
|
}, []);
|
|
1391
|
-
const applyResponse =
|
|
1851
|
+
const applyResponse = React11.useCallback(
|
|
1392
1852
|
(response) => {
|
|
1393
1853
|
if (response.status === "error" && !response.messages?.length) {
|
|
1394
1854
|
setError(response.message ?? "The assistant could not answer.");
|
|
@@ -1410,7 +1870,7 @@ function useAgentChat(config) {
|
|
|
1410
1870
|
},
|
|
1411
1871
|
[]
|
|
1412
1872
|
);
|
|
1413
|
-
const handleStreamEvent =
|
|
1873
|
+
const handleStreamEvent = React11.useCallback(
|
|
1414
1874
|
(event, ctx) => {
|
|
1415
1875
|
switch (event.type) {
|
|
1416
1876
|
case "conversation":
|
|
@@ -1475,7 +1935,7 @@ function useAgentChat(config) {
|
|
|
1475
1935
|
[]
|
|
1476
1936
|
);
|
|
1477
1937
|
const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
|
|
1478
|
-
const send =
|
|
1938
|
+
const send = React11.useCallback(
|
|
1479
1939
|
async (text, files) => {
|
|
1480
1940
|
if (sending) return;
|
|
1481
1941
|
if (!text.trim() && !(files && files.length)) return;
|
|
@@ -1506,7 +1966,14 @@ function useAgentChat(config) {
|
|
|
1506
1966
|
},
|
|
1507
1967
|
[applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
|
|
1508
1968
|
);
|
|
1509
|
-
const
|
|
1969
|
+
const transcribe = React11.useCallback(
|
|
1970
|
+
async (audio) => {
|
|
1971
|
+
const { text } = await client.transcribe(audio);
|
|
1972
|
+
return text;
|
|
1973
|
+
},
|
|
1974
|
+
[client]
|
|
1975
|
+
);
|
|
1976
|
+
const resolveApproval = React11.useCallback(
|
|
1510
1977
|
async (callId, approve) => {
|
|
1511
1978
|
if (resolvingApprovalId) return;
|
|
1512
1979
|
setResolvingApprovalId(callId);
|
|
@@ -1535,13 +2002,13 @@ function useAgentChat(config) {
|
|
|
1535
2002
|
},
|
|
1536
2003
|
[applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
|
|
1537
2004
|
);
|
|
1538
|
-
const newChat =
|
|
2005
|
+
const newChat = React11.useCallback(() => {
|
|
1539
2006
|
setConversationId(null);
|
|
1540
2007
|
setMessages([]);
|
|
1541
2008
|
setPendingApprovals([]);
|
|
1542
2009
|
setError(null);
|
|
1543
2010
|
}, []);
|
|
1544
|
-
const refreshConversations =
|
|
2011
|
+
const refreshConversations = React11.useCallback(async () => {
|
|
1545
2012
|
try {
|
|
1546
2013
|
const { conversations: list } = await client.listConversations();
|
|
1547
2014
|
setConversations(list);
|
|
@@ -1551,7 +2018,7 @@ function useAgentChat(config) {
|
|
|
1551
2018
|
return [];
|
|
1552
2019
|
}
|
|
1553
2020
|
}, [client, handleFailure]);
|
|
1554
|
-
const openConversation =
|
|
2021
|
+
const openConversation = React11.useCallback(
|
|
1555
2022
|
async (id) => {
|
|
1556
2023
|
setError(null);
|
|
1557
2024
|
try {
|
|
@@ -1565,7 +2032,7 @@ function useAgentChat(config) {
|
|
|
1565
2032
|
},
|
|
1566
2033
|
[client, handleFailure]
|
|
1567
2034
|
);
|
|
1568
|
-
const deleteConversation =
|
|
2035
|
+
const deleteConversation = React11.useCallback(
|
|
1569
2036
|
async (id) => {
|
|
1570
2037
|
try {
|
|
1571
2038
|
await client.deleteConversation(id);
|
|
@@ -1577,7 +2044,7 @@ function useAgentChat(config) {
|
|
|
1577
2044
|
},
|
|
1578
2045
|
[client, conversationId, handleFailure, newChat]
|
|
1579
2046
|
);
|
|
1580
|
-
const clearError =
|
|
2047
|
+
const clearError = React11.useCallback(() => setError(null), []);
|
|
1581
2048
|
return {
|
|
1582
2049
|
agentInfo,
|
|
1583
2050
|
clearError,
|
|
@@ -1593,7 +2060,8 @@ function useAgentChat(config) {
|
|
|
1593
2060
|
resolveApproval,
|
|
1594
2061
|
resolvingApprovalId,
|
|
1595
2062
|
send,
|
|
1596
|
-
sending
|
|
2063
|
+
sending,
|
|
2064
|
+
transcribe
|
|
1597
2065
|
};
|
|
1598
2066
|
}
|
|
1599
2067
|
var STORAGE_PREFIX = "cc-agent-always-approve:";
|
|
@@ -1612,11 +2080,11 @@ function readStored(clientId) {
|
|
|
1612
2080
|
}
|
|
1613
2081
|
}
|
|
1614
2082
|
function useAlwaysApprove(clientId) {
|
|
1615
|
-
const [approved, setApproved] =
|
|
1616
|
-
|
|
2083
|
+
const [approved, setApproved] = React11.useState(() => /* @__PURE__ */ new Set());
|
|
2084
|
+
React11.useEffect(() => {
|
|
1617
2085
|
setApproved(readStored(clientId));
|
|
1618
2086
|
}, [clientId]);
|
|
1619
|
-
const persist =
|
|
2087
|
+
const persist = React11.useCallback(
|
|
1620
2088
|
(next) => {
|
|
1621
2089
|
setApproved(next);
|
|
1622
2090
|
if (typeof window === "undefined") return;
|
|
@@ -1627,8 +2095,8 @@ function useAlwaysApprove(clientId) {
|
|
|
1627
2095
|
},
|
|
1628
2096
|
[clientId]
|
|
1629
2097
|
);
|
|
1630
|
-
const isAlwaysApproved =
|
|
1631
|
-
const setAlwaysApprove =
|
|
2098
|
+
const isAlwaysApproved = React11.useCallback((toolName) => approved.has(toolName), [approved]);
|
|
2099
|
+
const setAlwaysApprove = React11.useCallback(
|
|
1632
2100
|
(toolName) => {
|
|
1633
2101
|
if (approved.has(toolName)) return;
|
|
1634
2102
|
const next = new Set(approved);
|
|
@@ -1637,7 +2105,7 @@ function useAlwaysApprove(clientId) {
|
|
|
1637
2105
|
},
|
|
1638
2106
|
[approved, persist]
|
|
1639
2107
|
);
|
|
1640
|
-
const clearAlwaysApprove =
|
|
2108
|
+
const clearAlwaysApprove = React11.useCallback(
|
|
1641
2109
|
(toolName) => {
|
|
1642
2110
|
if (!approved.has(toolName)) return;
|
|
1643
2111
|
const next = new Set(approved);
|
|
@@ -1657,10 +2125,10 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1657
2125
|
const position = config.appearance?.position ?? "bottom-right";
|
|
1658
2126
|
const isControlled = controlledOpen !== void 0;
|
|
1659
2127
|
const storageKey2 = `cc-agent-open:${config.clientId}`;
|
|
1660
|
-
const [uncontrolledOpen, setUncontrolledOpen] =
|
|
2128
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React11.useState(false);
|
|
1661
2129
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1662
|
-
const [bootstrapDone, setBootstrapDone] =
|
|
1663
|
-
const persist =
|
|
2130
|
+
const [bootstrapDone, setBootstrapDone] = React11.useState(isControlled);
|
|
2131
|
+
const persist = React11.useCallback(
|
|
1664
2132
|
(next) => {
|
|
1665
2133
|
if (isControlled || !behavior.persistOpenState) return;
|
|
1666
2134
|
try {
|
|
@@ -1670,7 +2138,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1670
2138
|
},
|
|
1671
2139
|
[behavior.persistOpenState, isControlled, storageKey2]
|
|
1672
2140
|
);
|
|
1673
|
-
const setOpen =
|
|
2141
|
+
const setOpen = React11.useCallback(
|
|
1674
2142
|
(next) => {
|
|
1675
2143
|
setUncontrolledOpen(next);
|
|
1676
2144
|
persist(next);
|
|
@@ -1678,14 +2146,14 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1678
2146
|
},
|
|
1679
2147
|
[onOpenChange, persist]
|
|
1680
2148
|
);
|
|
1681
|
-
const [agentMode, setAgentMode] =
|
|
2149
|
+
const [agentMode, setAgentMode] = React11.useState(false);
|
|
1682
2150
|
const isDesktop = useMediaQuery("(min-width: 640px)", { defaultSSRValue: true, ssr: true });
|
|
1683
2151
|
const fullscreen = !isDesktop || agentMode;
|
|
1684
2152
|
const chat = useAgentChat(config);
|
|
1685
2153
|
const alwaysApprove = useAlwaysApprove(config.clientId);
|
|
1686
2154
|
const { messages, pendingApprovals, resolveApproval, resolvingApprovalId, sending } = chat;
|
|
1687
|
-
const bootRef =
|
|
1688
|
-
|
|
2155
|
+
const bootRef = React11.useRef(false);
|
|
2156
|
+
React11.useEffect(() => {
|
|
1689
2157
|
if (isControlled || bootRef.current) return;
|
|
1690
2158
|
bootRef.current = true;
|
|
1691
2159
|
let cancelled = false;
|
|
@@ -1714,15 +2182,15 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1714
2182
|
cancelled = true;
|
|
1715
2183
|
};
|
|
1716
2184
|
}, []);
|
|
1717
|
-
const seenCountRef =
|
|
1718
|
-
const [baselined, setBaselined] =
|
|
1719
|
-
const [anchorId, setAnchorId] =
|
|
1720
|
-
|
|
2185
|
+
const seenCountRef = React11.useRef(0);
|
|
2186
|
+
const [baselined, setBaselined] = React11.useState(false);
|
|
2187
|
+
const [anchorId, setAnchorId] = React11.useState(null);
|
|
2188
|
+
React11.useEffect(() => {
|
|
1721
2189
|
if (baselined || !bootstrapDone) return;
|
|
1722
2190
|
seenCountRef.current = messages.length;
|
|
1723
2191
|
setBaselined(true);
|
|
1724
2192
|
}, [baselined, bootstrapDone, messages.length]);
|
|
1725
|
-
|
|
2193
|
+
React11.useEffect(() => {
|
|
1726
2194
|
if (open) {
|
|
1727
2195
|
if (messages.length > seenCountRef.current) {
|
|
1728
2196
|
const firstUnread = messages[seenCountRef.current];
|
|
@@ -1732,7 +2200,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1732
2200
|
}
|
|
1733
2201
|
}, [open, messages]);
|
|
1734
2202
|
const unreadCount = open || !baselined ? 0 : messages.slice(seenCountRef.current).filter(isUnreadable).length;
|
|
1735
|
-
const workLabel =
|
|
2203
|
+
const workLabel = React11.useMemo(() => {
|
|
1736
2204
|
if (!sending) return null;
|
|
1737
2205
|
const last = messages[messages.length - 1];
|
|
1738
2206
|
if (last?.role === "tool") return "Calling tools\u2026";
|
|
@@ -1741,14 +2209,14 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1741
2209
|
}, [sending, messages]);
|
|
1742
2210
|
const approvalWaiting = pendingApprovals.length > 0;
|
|
1743
2211
|
const { isAlwaysApproved } = alwaysApprove;
|
|
1744
|
-
|
|
2212
|
+
React11.useEffect(() => {
|
|
1745
2213
|
if (resolvingApprovalId) return;
|
|
1746
2214
|
const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
|
|
1747
2215
|
if (target) void resolveApproval(target.id, true);
|
|
1748
2216
|
}, [pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
|
|
1749
|
-
const firedWriteIds =
|
|
2217
|
+
const firedWriteIds = React11.useRef(/* @__PURE__ */ new Set());
|
|
1750
2218
|
const { onWriteSuccess } = config;
|
|
1751
|
-
|
|
2219
|
+
React11.useEffect(() => {
|
|
1752
2220
|
if (!onWriteSuccess) return;
|
|
1753
2221
|
for (const message of messages) {
|
|
1754
2222
|
if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
|
|
@@ -1758,8 +2226,8 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1758
2226
|
onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
|
|
1759
2227
|
}
|
|
1760
2228
|
}, [messages, onWriteSuccess]);
|
|
1761
|
-
const lastNavigatedMessageId =
|
|
1762
|
-
|
|
2229
|
+
const lastNavigatedMessageId = React11.useRef(null);
|
|
2230
|
+
React11.useEffect(() => {
|
|
1763
2231
|
if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
|
|
1764
2232
|
const toolMessages = messages.filter((message) => message.role === "tool");
|
|
1765
2233
|
const latest = toolMessages[toolMessages.length - 1];
|
|
@@ -1771,7 +2239,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1771
2239
|
const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
|
|
1772
2240
|
if (path) config.navigate(path);
|
|
1773
2241
|
}, [config, fullscreen, messages, open]);
|
|
1774
|
-
const closePanel =
|
|
2242
|
+
const closePanel = React11.useCallback(() => {
|
|
1775
2243
|
setOpen(false);
|
|
1776
2244
|
setAgentMode(false);
|
|
1777
2245
|
}, [setOpen]);
|
|
@@ -1918,6 +2386,6 @@ function defineAgentConfig(config) {
|
|
|
1918
2386
|
return config;
|
|
1919
2387
|
}
|
|
1920
2388
|
|
|
1921
|
-
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 };
|
|
2389
|
+
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 };
|
|
1922
2390
|
//# sourceMappingURL=index.js.map
|
|
1923
2391
|
//# sourceMappingURL=index.js.map
|