@wallavi/widget 1.5.3 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +88 -2
- package/dist/index.d.ts +88 -2
- package/dist/index.js +570 -114
- package/dist/index.mjs +571 -117
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
2
|
-
import { X, RotateCcw, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
1
|
+
import { useRef, useEffect, useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { UploadCloud, X, RotateCcw, Loader2, Square, Mic, Paperclip, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle, Search, Check, FileText, FileSpreadsheet } from 'lucide-react';
|
|
3
3
|
import { clsx } from 'clsx';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
5
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
@@ -186,7 +186,7 @@ function useChat({
|
|
|
186
186
|
});
|
|
187
187
|
}, []);
|
|
188
188
|
const fetchAndStream = useCallback(async (opts) => {
|
|
189
|
-
const { input: userInput, msgId, extraMetadata } = opts;
|
|
189
|
+
const { input: userInput, msgId, extraMetadata, attachments } = opts;
|
|
190
190
|
const isPrivate = Boolean(workspaceId);
|
|
191
191
|
const token = isPrivate && typeof window !== "undefined" ? await window.Clerk?.session?.getToken() : null;
|
|
192
192
|
const url = isPrivate ? `${API_URL}/api/threads/${threadId}/stream` : `${API_URL}/api/chat/stream`;
|
|
@@ -201,6 +201,7 @@ function useChat({
|
|
|
201
201
|
agentId,
|
|
202
202
|
...isPrivate ? { workspaceId, ...playgroundOverrides ? { playgroundOverrides } : {} } : { threadId },
|
|
203
203
|
source,
|
|
204
|
+
...attachments?.length ? { attachments } : {},
|
|
204
205
|
...userContext?.userName ? { userName: userContext.userName } : {},
|
|
205
206
|
...userContext?.userEmail ? { userEmail: userContext.userEmail } : {},
|
|
206
207
|
userMetadata: {
|
|
@@ -221,22 +222,30 @@ function useChat({
|
|
|
221
222
|
if (!res.body) throw new Error("No stream body");
|
|
222
223
|
await consumeStream(res.body, (proto) => applyStreamEvent(proto, msgId));
|
|
223
224
|
}, [agentId, workspaceId, source, threadId, userContext, playgroundOverrides, applyStreamEvent]);
|
|
225
|
+
const pendingAttachmentsRef = useRef([]);
|
|
224
226
|
const send = useCallback(
|
|
225
227
|
async (text) => {
|
|
226
228
|
const userInput = (text ?? input).trim();
|
|
227
229
|
if (!userInput || streaming) return;
|
|
228
230
|
setInput("");
|
|
231
|
+
const attachments = pendingAttachmentsRef.current.length > 0 ? [...pendingAttachmentsRef.current] : void 0;
|
|
232
|
+
pendingAttachmentsRef.current = [];
|
|
229
233
|
const userMsgId = newId();
|
|
230
234
|
setMessages((prev) => [
|
|
231
235
|
...prev,
|
|
232
|
-
{
|
|
236
|
+
{
|
|
237
|
+
id: userMsgId,
|
|
238
|
+
role: "user",
|
|
239
|
+
parts: [{ type: "text", text: userInput }],
|
|
240
|
+
...attachments ? { attachments } : {}
|
|
241
|
+
}
|
|
233
242
|
]);
|
|
234
243
|
setStreaming(true);
|
|
235
244
|
const assistantMsgId = newId();
|
|
236
245
|
streamingMsgIdRef.current = assistantMsgId;
|
|
237
246
|
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
238
247
|
try {
|
|
239
|
-
await fetchAndStream({ input: userInput, msgId: assistantMsgId });
|
|
248
|
+
await fetchAndStream({ input: userInput, msgId: assistantMsgId, attachments });
|
|
240
249
|
} catch {
|
|
241
250
|
setMessages((prev) => {
|
|
242
251
|
const idx = prev.findIndex((m) => m.id === assistantMsgId);
|
|
@@ -286,8 +295,11 @@ function useChat({
|
|
|
286
295
|
setStreaming(false);
|
|
287
296
|
streamingMsgIdRef.current = null;
|
|
288
297
|
},
|
|
289
|
-
[streaming, fetchAndStream]
|
|
298
|
+
[input, streaming, fetchAndStream]
|
|
290
299
|
);
|
|
300
|
+
const queueAttachments = useCallback((payloads) => {
|
|
301
|
+
pendingAttachmentsRef.current = payloads;
|
|
302
|
+
}, []);
|
|
291
303
|
const regenerate = useCallback(async () => {
|
|
292
304
|
if (streaming) return;
|
|
293
305
|
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
@@ -301,7 +313,176 @@ function useChat({
|
|
|
301
313
|
});
|
|
302
314
|
await send(lastText);
|
|
303
315
|
}, [streaming, messages, send]);
|
|
304
|
-
return { messages, input, setInput, streaming, threadId, send, regenerate, reset, selectPickerOption };
|
|
316
|
+
return { messages, input, setInput, streaming, threadId, send, queueAttachments, regenerate, reset, selectPickerOption };
|
|
317
|
+
}
|
|
318
|
+
function getPreferredMimeType() {
|
|
319
|
+
if (typeof MediaRecorder === "undefined") return "";
|
|
320
|
+
const candidates = [
|
|
321
|
+
"audio/webm;codecs=opus",
|
|
322
|
+
"audio/webm",
|
|
323
|
+
"audio/ogg;codecs=opus",
|
|
324
|
+
"audio/ogg",
|
|
325
|
+
"audio/mp4"
|
|
326
|
+
];
|
|
327
|
+
return candidates.find((t) => MediaRecorder.isTypeSupported(t)) ?? "";
|
|
328
|
+
}
|
|
329
|
+
function mimeTypeToExtension(mimeType) {
|
|
330
|
+
if (mimeType.includes("ogg")) return "ogg";
|
|
331
|
+
if (mimeType.includes("mp4")) return "mp4";
|
|
332
|
+
return "webm";
|
|
333
|
+
}
|
|
334
|
+
var DEFAULT_API_URL = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
335
|
+
function useVoice({ agentId, apiUrl, onTranscript, onError }) {
|
|
336
|
+
const [voiceState, setVoiceState] = useState("idle");
|
|
337
|
+
const recorderRef = useRef(null);
|
|
338
|
+
const chunksRef = useRef([]);
|
|
339
|
+
const streamRef = useRef(null);
|
|
340
|
+
const errorTimerRef = useRef(null);
|
|
341
|
+
const isSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined" && !!navigator?.mediaDevices?.getUserMedia;
|
|
342
|
+
const base = apiUrl ?? DEFAULT_API_URL;
|
|
343
|
+
const transcribeBlob = useCallback(
|
|
344
|
+
async (blob, mimeType) => {
|
|
345
|
+
setVoiceState("transcribing");
|
|
346
|
+
try {
|
|
347
|
+
const ext = mimeTypeToExtension(mimeType);
|
|
348
|
+
const form = new FormData();
|
|
349
|
+
form.append("audio", blob, `recording.${ext}`);
|
|
350
|
+
form.append("agentId", agentId);
|
|
351
|
+
const res = await fetch(`${base}/api/chat/transcribe`, { method: "POST", body: form });
|
|
352
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
353
|
+
const data = await res.json();
|
|
354
|
+
if (!data.text?.trim()) throw new Error(data.error ?? "Empty transcript");
|
|
355
|
+
onTranscript(data.text.trim());
|
|
356
|
+
setVoiceState("idle");
|
|
357
|
+
} catch (err) {
|
|
358
|
+
const msg = err instanceof Error ? err.message : "Transcription failed";
|
|
359
|
+
onError?.(msg);
|
|
360
|
+
setVoiceState("error");
|
|
361
|
+
errorTimerRef.current = setTimeout(() => setVoiceState("idle"), 2500);
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
[agentId, base, onTranscript, onError]
|
|
365
|
+
);
|
|
366
|
+
const start = useCallback(async () => {
|
|
367
|
+
if (!isSupported || voiceState !== "idle") return;
|
|
368
|
+
try {
|
|
369
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
370
|
+
streamRef.current = stream;
|
|
371
|
+
const mimeType = getPreferredMimeType();
|
|
372
|
+
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
|
|
373
|
+
chunksRef.current = [];
|
|
374
|
+
recorder.ondataavailable = (e) => {
|
|
375
|
+
if (e.data.size > 0) chunksRef.current.push(e.data);
|
|
376
|
+
};
|
|
377
|
+
recorder.onstop = async () => {
|
|
378
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
379
|
+
const blob = new Blob(chunksRef.current, { type: mimeType || "audio/webm" });
|
|
380
|
+
if (blob.size === 0) {
|
|
381
|
+
onError?.("Recording was empty \u2014 please try again.");
|
|
382
|
+
setVoiceState("idle");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
await transcribeBlob(blob, mimeType || "audio/webm");
|
|
386
|
+
};
|
|
387
|
+
recorder.start(250);
|
|
388
|
+
recorderRef.current = recorder;
|
|
389
|
+
setVoiceState("recording");
|
|
390
|
+
} catch (err) {
|
|
391
|
+
const msg = err instanceof Error ? err.message : "Microphone access denied";
|
|
392
|
+
onError?.(msg);
|
|
393
|
+
setVoiceState("idle");
|
|
394
|
+
}
|
|
395
|
+
}, [isSupported, voiceState, transcribeBlob, onError]);
|
|
396
|
+
const stop = useCallback(() => {
|
|
397
|
+
if (recorderRef.current?.state === "recording") {
|
|
398
|
+
recorderRef.current.stop();
|
|
399
|
+
recorderRef.current = null;
|
|
400
|
+
}
|
|
401
|
+
}, []);
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
return () => {
|
|
404
|
+
if (errorTimerRef.current) clearTimeout(errorTimerRef.current);
|
|
405
|
+
if (recorderRef.current?.state === "recording") recorderRef.current.stop();
|
|
406
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
407
|
+
};
|
|
408
|
+
}, []);
|
|
409
|
+
return { voiceState, isSupported, start, stop };
|
|
410
|
+
}
|
|
411
|
+
var DEFAULT_API_URL2 = process.env.NEXT_PUBLIC_API_URL ?? "https://wallavi-production.up.railway.app";
|
|
412
|
+
function makeId() {
|
|
413
|
+
return `att_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
414
|
+
}
|
|
415
|
+
function useAttachments({
|
|
416
|
+
agentId,
|
|
417
|
+
apiUrl,
|
|
418
|
+
maxFiles = 5
|
|
419
|
+
}) {
|
|
420
|
+
const [attachments, setAttachments] = useState([]);
|
|
421
|
+
const base = apiUrl ?? DEFAULT_API_URL2;
|
|
422
|
+
const uploadOne = useCallback(
|
|
423
|
+
async (file, id) => {
|
|
424
|
+
const form = new FormData();
|
|
425
|
+
form.append("file", file);
|
|
426
|
+
form.append("agentId", agentId);
|
|
427
|
+
try {
|
|
428
|
+
const res = await fetch(`${base}/api/chat/upload`, { method: "POST", body: form });
|
|
429
|
+
if (!res.ok) {
|
|
430
|
+
const data = await res.json().catch(() => ({}));
|
|
431
|
+
setAttachments(
|
|
432
|
+
(prev) => prev.map(
|
|
433
|
+
(a) => a.id === id ? {
|
|
434
|
+
...a,
|
|
435
|
+
status: "error",
|
|
436
|
+
errorMessage: data.error ?? `Upload failed (${res.status})`
|
|
437
|
+
} : a
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const payload = await res.json();
|
|
443
|
+
setAttachments(
|
|
444
|
+
(prev) => prev.map((a) => a.id === id ? { ...a, status: "ready", payload } : a)
|
|
445
|
+
);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
const msg = err instanceof Error ? err.message : "Upload failed";
|
|
448
|
+
setAttachments(
|
|
449
|
+
(prev) => prev.map(
|
|
450
|
+
(a) => a.id === id ? { ...a, status: "error", errorMessage: msg } : a
|
|
451
|
+
)
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
[agentId, base]
|
|
456
|
+
);
|
|
457
|
+
const attach = useCallback(
|
|
458
|
+
(files) => {
|
|
459
|
+
const list = Array.from(files);
|
|
460
|
+
setAttachments((prev) => {
|
|
461
|
+
const remaining = maxFiles - prev.filter((a) => a.status !== "error").length;
|
|
462
|
+
if (remaining <= 0) return prev;
|
|
463
|
+
const toAdd = list.slice(0, remaining).map((f) => {
|
|
464
|
+
const id = makeId();
|
|
465
|
+
void uploadOne(f, id);
|
|
466
|
+
return {
|
|
467
|
+
id,
|
|
468
|
+
name: f.name,
|
|
469
|
+
mimeType: f.type,
|
|
470
|
+
sizeBytes: f.size,
|
|
471
|
+
status: "uploading"
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
return [...prev, ...toAdd];
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
[maxFiles, uploadOne]
|
|
478
|
+
);
|
|
479
|
+
const remove = useCallback((id) => {
|
|
480
|
+
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
481
|
+
}, []);
|
|
482
|
+
const clear = useCallback(() => setAttachments([]), []);
|
|
483
|
+
const isUploading = attachments.some((a) => a.status === "uploading");
|
|
484
|
+
const readyPayloads = attachments.filter((a) => a.status === "ready" && a.payload).map((a) => a.payload);
|
|
485
|
+
return { attachments, attach, remove, clear, isUploading, readyPayloads };
|
|
305
486
|
}
|
|
306
487
|
function DefaultIcon() {
|
|
307
488
|
return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
|
|
@@ -404,10 +585,112 @@ function ChatHeader({
|
|
|
404
585
|
}
|
|
405
586
|
);
|
|
406
587
|
}
|
|
588
|
+
var cn2 = (...inputs) => twMerge(clsx(inputs));
|
|
589
|
+
function formatBytes(bytes) {
|
|
590
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
591
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
592
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
593
|
+
}
|
|
594
|
+
function ChipLeading({ a }) {
|
|
595
|
+
if (a.status === "uploading") return /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 shrink-0 animate-spin" });
|
|
596
|
+
if (a.status === "error") return /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 shrink-0" });
|
|
597
|
+
const thumbSrc = a.payload?.url ?? a.payload?.base64;
|
|
598
|
+
if (a.mimeType.startsWith("image/") && thumbSrc) {
|
|
599
|
+
return /* @__PURE__ */ jsx(
|
|
600
|
+
"img",
|
|
601
|
+
{
|
|
602
|
+
src: thumbSrc,
|
|
603
|
+
alt: a.name,
|
|
604
|
+
className: "h-6 w-6 rounded object-cover shrink-0 border border-border/40"
|
|
605
|
+
}
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (a.mimeType.startsWith("image/")) return /* @__PURE__ */ jsx("div", { className: "h-6 w-6 rounded bg-muted-foreground/10 border border-border/30 shrink-0 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-[8px] font-semibold text-muted-foreground/50", children: "IMG" }) });
|
|
609
|
+
if (a.mimeType === "application/pdf") return /* @__PURE__ */ jsx(FileText, { className: "h-3 w-3 shrink-0 text-red-400" });
|
|
610
|
+
if (a.mimeType.includes("csv") || a.mimeType.includes("spreadsheet") || a.name.endsWith(".csv")) {
|
|
611
|
+
return /* @__PURE__ */ jsx(FileSpreadsheet, { className: "h-3 w-3 shrink-0 text-emerald-500" });
|
|
612
|
+
}
|
|
613
|
+
return /* @__PURE__ */ jsx(FileText, { className: "h-3 w-3 shrink-0" });
|
|
614
|
+
}
|
|
615
|
+
function AttachmentChips({ attachments, onRemove }) {
|
|
616
|
+
if (attachments.length === 0) return null;
|
|
617
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pb-1.5", children: attachments.map((a) => /* @__PURE__ */ jsxs(
|
|
618
|
+
"div",
|
|
619
|
+
{
|
|
620
|
+
className: cn2(
|
|
621
|
+
"flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[11px] max-w-[200px]",
|
|
622
|
+
a.status === "error" ? "border-red-200 bg-red-50 text-red-600 dark:border-red-800 dark:bg-red-950 dark:text-red-400" : "border-border bg-muted/60 text-muted-foreground"
|
|
623
|
+
),
|
|
624
|
+
children: [
|
|
625
|
+
/* @__PURE__ */ jsx(ChipLeading, { a }),
|
|
626
|
+
/* @__PURE__ */ jsx("span", { className: "truncate leading-tight", children: a.status === "error" ? a.errorMessage ?? "Upload failed" : a.name }),
|
|
627
|
+
a.status === "ready" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground/50", children: formatBytes(a.sizeBytes) }),
|
|
628
|
+
/* @__PURE__ */ jsx(
|
|
629
|
+
"button",
|
|
630
|
+
{
|
|
631
|
+
type: "button",
|
|
632
|
+
onClick: () => onRemove(a.id),
|
|
633
|
+
className: "shrink-0 rounded-full p-0.5 hover:bg-foreground/10 transition-colors ml-0.5",
|
|
634
|
+
title: "Remove",
|
|
635
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
]
|
|
639
|
+
},
|
|
640
|
+
a.id
|
|
641
|
+
)) });
|
|
642
|
+
}
|
|
407
643
|
var Avatar2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
408
644
|
var AvatarImage2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
409
645
|
var AvatarFallback2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
|
|
410
646
|
var ReactMarkdown = ReactMarkdownLib;
|
|
647
|
+
function SentAttachments({ attachments, contrastColor }) {
|
|
648
|
+
const images = attachments.filter((a) => a.contentType === "image");
|
|
649
|
+
const files = attachments.filter((a) => a.contentType !== "image");
|
|
650
|
+
const isDark = contrastColor === "#ffffff" || contrastColor === "#fff";
|
|
651
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 w-full", children: [
|
|
652
|
+
images.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("flex gap-1.5 flex-wrap", images.length === 1 && ""), children: images.map((img) => {
|
|
653
|
+
const src = img.url ?? img.base64;
|
|
654
|
+
return src ? /* @__PURE__ */ jsx(
|
|
655
|
+
"img",
|
|
656
|
+
{
|
|
657
|
+
src,
|
|
658
|
+
alt: img.name,
|
|
659
|
+
className: cn(
|
|
660
|
+
"rounded-xl object-cover border",
|
|
661
|
+
images.length === 1 ? "w-full max-h-48" : "h-20 w-20",
|
|
662
|
+
isDark ? "border-white/20" : "border-black/10"
|
|
663
|
+
)
|
|
664
|
+
},
|
|
665
|
+
img.id
|
|
666
|
+
) : /* @__PURE__ */ jsx(
|
|
667
|
+
"div",
|
|
668
|
+
{
|
|
669
|
+
className: cn(
|
|
670
|
+
"h-20 w-20 rounded-xl flex items-center justify-center text-[10px] font-medium",
|
|
671
|
+
isDark ? "bg-white/10 text-white/60" : "bg-black/10 text-black/40"
|
|
672
|
+
),
|
|
673
|
+
children: "IMG"
|
|
674
|
+
},
|
|
675
|
+
img.id
|
|
676
|
+
);
|
|
677
|
+
}) }),
|
|
678
|
+
files.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: files.map((f) => /* @__PURE__ */ jsxs(
|
|
679
|
+
"div",
|
|
680
|
+
{
|
|
681
|
+
className: cn(
|
|
682
|
+
"flex items-center gap-1 rounded-lg px-2 py-1 text-[11px]",
|
|
683
|
+
isDark ? "bg-white/15 text-white/80" : "bg-black/10 text-black/60"
|
|
684
|
+
),
|
|
685
|
+
children: [
|
|
686
|
+
/* @__PURE__ */ jsx("span", { className: "truncate max-w-[120px]", children: f.name }),
|
|
687
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-60 shrink-0", children: formatBytes(f.sizeBytes) })
|
|
688
|
+
]
|
|
689
|
+
},
|
|
690
|
+
f.id
|
|
691
|
+
)) })
|
|
692
|
+
] });
|
|
693
|
+
}
|
|
411
694
|
function ThinkingDots() {
|
|
412
695
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
413
696
|
/* @__PURE__ */ jsx("style", { children: `
|
|
@@ -475,83 +758,112 @@ function ReasoningBlock({ text }) {
|
|
|
475
758
|
open && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground/80 whitespace-pre-wrap border-l-2 border-muted pl-2 leading-relaxed", children: text })
|
|
476
759
|
] });
|
|
477
760
|
}
|
|
478
|
-
function PickerOption({
|
|
479
|
-
opt,
|
|
480
|
-
isSelected,
|
|
481
|
-
isConsumed,
|
|
482
|
-
actionDisabled,
|
|
483
|
-
pickerId,
|
|
484
|
-
paramName,
|
|
485
|
-
onSelect
|
|
486
|
-
}) {
|
|
487
|
-
const [hovered, setHovered] = useState(false);
|
|
488
|
-
const inactive = actionDisabled || isConsumed;
|
|
489
|
-
return /* @__PURE__ */ jsx(
|
|
490
|
-
"button",
|
|
491
|
-
{
|
|
492
|
-
disabled: inactive,
|
|
493
|
-
onMouseEnter: () => setHovered(true),
|
|
494
|
-
onMouseLeave: () => setHovered(false),
|
|
495
|
-
onClick: () => {
|
|
496
|
-
if (!inactive) onSelect(pickerId, paramName, opt.value, opt.label);
|
|
497
|
-
},
|
|
498
|
-
style: {
|
|
499
|
-
fontSize: 13,
|
|
500
|
-
lineHeight: "1.4",
|
|
501
|
-
borderRadius: 100,
|
|
502
|
-
padding: "6px 14px",
|
|
503
|
-
border: "none",
|
|
504
|
-
backgroundColor: isSelected ? "var(--primary, #19191c)" : hovered && !inactive ? "rgba(0,0,0,0.14)" : "rgba(0,0,0,0.08)",
|
|
505
|
-
color: isSelected ? "var(--primary-foreground, #fff)" : "var(--foreground, #09090b)",
|
|
506
|
-
cursor: inactive ? "default" : "pointer",
|
|
507
|
-
opacity: isConsumed ? 0.3 : 1,
|
|
508
|
-
transition: "background-color 0.12s ease, opacity 0.12s ease",
|
|
509
|
-
fontWeight: isSelected ? 600 : 500,
|
|
510
|
-
boxShadow: "none"
|
|
511
|
-
},
|
|
512
|
-
children: opt.label
|
|
513
|
-
}
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
761
|
function PickerSelector({
|
|
517
762
|
part,
|
|
518
763
|
disabled,
|
|
519
764
|
onSelect
|
|
520
765
|
}) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
766
|
+
const count = part.options.length;
|
|
767
|
+
const mode = count <= 6 ? "pills" : count <= 20 ? "grid" : "list";
|
|
768
|
+
const [query, setQuery] = useState("");
|
|
769
|
+
const filtered = useMemo(() => {
|
|
770
|
+
if (!query.trim()) return part.options;
|
|
771
|
+
const q = query.toLowerCase();
|
|
772
|
+
return part.options.filter((o) => o.label.toLowerCase().includes(q));
|
|
773
|
+
}, [part.options, query]);
|
|
774
|
+
const isConsumed = !!part.selectedValue;
|
|
775
|
+
const handleClick = (opt) => {
|
|
776
|
+
if (!disabled && !isConsumed) onSelect(part.pickerId, part.paramName, opt.value, opt.label);
|
|
777
|
+
};
|
|
778
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
779
|
+
"flex flex-col gap-2.5 rounded-xl rounded-tl-none border bg-muted/60 p-3",
|
|
780
|
+
"border-border/50"
|
|
781
|
+
), children: [
|
|
782
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10.5px] font-semibold uppercase tracking-widest text-muted-foreground/70 leading-none", children: part.label }),
|
|
783
|
+
mode === "list" && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
784
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground/50 pointer-events-none" }),
|
|
785
|
+
/* @__PURE__ */ jsx(
|
|
786
|
+
"input",
|
|
787
|
+
{
|
|
788
|
+
type: "text",
|
|
789
|
+
value: query,
|
|
790
|
+
onChange: (e) => setQuery(e.target.value),
|
|
791
|
+
placeholder: "Search\u2026",
|
|
792
|
+
disabled: disabled || isConsumed,
|
|
793
|
+
className: "w-full rounded-lg border bg-background pl-7 pr-3 py-1.5 text-xs outline-none focus:ring-1 focus:ring-ring/40 placeholder:text-muted-foreground/40 disabled:opacity-50"
|
|
794
|
+
}
|
|
795
|
+
)
|
|
796
|
+
] }),
|
|
797
|
+
mode === "pills" && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: part.options.map((opt) => {
|
|
798
|
+
const sel = part.selectedValue === opt.value;
|
|
799
|
+
const faded = isConsumed && !sel;
|
|
800
|
+
return /* @__PURE__ */ jsxs(
|
|
801
|
+
"button",
|
|
802
|
+
{
|
|
803
|
+
disabled: disabled || isConsumed,
|
|
804
|
+
onClick: () => handleClick(opt),
|
|
805
|
+
className: cn(
|
|
806
|
+
"inline-flex items-center gap-1 rounded-full px-3.5 py-1.5 text-[12.5px] font-medium transition-all duration-150 select-none",
|
|
807
|
+
sel ? "bg-foreground text-background shadow-sm" : faded ? "bg-background/50 text-muted-foreground opacity-35 cursor-default" : "bg-background border border-border/70 text-foreground hover:border-foreground/30 hover:bg-background cursor-pointer",
|
|
808
|
+
disabled && !isConsumed && "opacity-60 pointer-events-none"
|
|
809
|
+
),
|
|
810
|
+
children: [
|
|
811
|
+
sel && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 shrink-0" }),
|
|
812
|
+
opt.label
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
opt.value
|
|
816
|
+
);
|
|
817
|
+
}) }),
|
|
818
|
+
mode === "grid" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-1.5 max-h-[168px] overflow-y-auto pr-0.5 scrollbar-thin", children: part.options.map((opt) => {
|
|
819
|
+
const sel = part.selectedValue === opt.value;
|
|
820
|
+
const faded = isConsumed && !sel;
|
|
821
|
+
return /* @__PURE__ */ jsxs(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
disabled: disabled || isConsumed,
|
|
825
|
+
onClick: () => handleClick(opt),
|
|
826
|
+
className: cn(
|
|
827
|
+
"flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[12px] font-medium text-left transition-all duration-150 select-none truncate",
|
|
828
|
+
sel ? "bg-foreground text-background" : faded ? "bg-background/40 text-muted-foreground opacity-35 cursor-default" : "bg-background border border-border/60 text-foreground hover:border-foreground/25 cursor-pointer",
|
|
829
|
+
disabled && !isConsumed && "opacity-60 pointer-events-none"
|
|
830
|
+
),
|
|
831
|
+
children: [
|
|
832
|
+
sel ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx("span", { className: "h-3 w-3 shrink-0" }),
|
|
833
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: opt.label })
|
|
834
|
+
]
|
|
835
|
+
},
|
|
836
|
+
opt.value
|
|
837
|
+
);
|
|
838
|
+
}) }),
|
|
839
|
+
mode === "list" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 max-h-[180px] overflow-y-auto pr-0.5", children: [
|
|
840
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "py-3 text-center text-xs text-muted-foreground/50", children: "No results" }),
|
|
841
|
+
filtered.map((opt) => {
|
|
842
|
+
const sel = part.selectedValue === opt.value;
|
|
843
|
+
const faded = isConsumed && !sel;
|
|
844
|
+
return /* @__PURE__ */ jsxs(
|
|
845
|
+
"button",
|
|
846
|
+
{
|
|
847
|
+
disabled: disabled || isConsumed,
|
|
848
|
+
onClick: () => handleClick(opt),
|
|
849
|
+
className: cn(
|
|
850
|
+
"flex items-center gap-2 rounded-lg px-2.5 py-2 text-[12.5px] text-left transition-all duration-100 select-none",
|
|
851
|
+
sel ? "bg-foreground text-background font-medium" : faded ? "opacity-30 cursor-default" : "hover:bg-background text-foreground cursor-pointer",
|
|
852
|
+
disabled && !isConsumed && "opacity-60 pointer-events-none"
|
|
853
|
+
),
|
|
854
|
+
children: [
|
|
855
|
+
/* @__PURE__ */ jsx("span", { className: cn(
|
|
856
|
+
"flex h-4 w-4 shrink-0 items-center justify-center rounded-full border text-[10px]",
|
|
857
|
+
sel ? "border-background bg-background/20" : "border-border/50"
|
|
858
|
+
), children: sel && /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }) }),
|
|
859
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: opt.label })
|
|
860
|
+
]
|
|
861
|
+
},
|
|
862
|
+
opt.value
|
|
863
|
+
);
|
|
864
|
+
})
|
|
865
|
+
] })
|
|
866
|
+
] });
|
|
555
867
|
}
|
|
556
868
|
function MessageBubble({
|
|
557
869
|
message,
|
|
@@ -569,12 +881,15 @@ function MessageBubble({
|
|
|
569
881
|
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
570
882
|
const contrastColor = getContrastColor(userColor);
|
|
571
883
|
if (isUser) {
|
|
572
|
-
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */
|
|
884
|
+
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
573
885
|
"div",
|
|
574
886
|
{
|
|
575
|
-
className: "max-w-[78%] rounded-2xl rounded-tr-sm px-4 py-2.5 text-sm leading-relaxed",
|
|
887
|
+
className: "max-w-[78%] rounded-2xl rounded-tr-sm px-4 py-2.5 text-sm leading-relaxed flex flex-col gap-2",
|
|
576
888
|
style: { backgroundColor: userColor, color: contrastColor },
|
|
577
|
-
children:
|
|
889
|
+
children: [
|
|
890
|
+
message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx(SentAttachments, { attachments: message.attachments, contrastColor }),
|
|
891
|
+
textPart?.text && /* @__PURE__ */ jsx("span", { children: textPart.text })
|
|
892
|
+
]
|
|
578
893
|
}
|
|
579
894
|
) });
|
|
580
895
|
}
|
|
@@ -679,7 +994,7 @@ function ChatMessages({
|
|
|
679
994
|
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
680
995
|
] });
|
|
681
996
|
}
|
|
682
|
-
var
|
|
997
|
+
var cn3 = (...inputs) => twMerge(clsx(inputs));
|
|
683
998
|
function ChatInput({
|
|
684
999
|
input,
|
|
685
1000
|
setInput,
|
|
@@ -688,8 +1003,18 @@ function ChatInput({
|
|
|
688
1003
|
placeholder,
|
|
689
1004
|
accentColor,
|
|
690
1005
|
canRegenerate = false,
|
|
691
|
-
onRegenerate
|
|
1006
|
+
onRegenerate,
|
|
1007
|
+
voiceState,
|
|
1008
|
+
onVoiceStart,
|
|
1009
|
+
onVoiceStop,
|
|
1010
|
+
attachments,
|
|
1011
|
+
onAttach,
|
|
1012
|
+
onRemoveAttachment,
|
|
1013
|
+
isUploading = false
|
|
692
1014
|
}) {
|
|
1015
|
+
const hasVoice = onVoiceStart !== void 0;
|
|
1016
|
+
const hasAttachments = onAttach !== void 0;
|
|
1017
|
+
const fileInputRef = useRef(null);
|
|
693
1018
|
const textareaRef = useRef(null);
|
|
694
1019
|
useEffect(() => {
|
|
695
1020
|
if (!input && textareaRef.current) {
|
|
@@ -721,39 +1046,102 @@ function ChatInput({
|
|
|
721
1046
|
]
|
|
722
1047
|
}
|
|
723
1048
|
),
|
|
724
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
725
|
-
/* @__PURE__ */ jsx(
|
|
726
|
-
|
|
1049
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col rounded-2xl border bg-background shadow-sm focus-within:ring-1 focus-within:ring-ring/40 transition-shadow", children: [
|
|
1050
|
+
hasAttachments && attachments && attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "px-2 pt-2", children: /* @__PURE__ */ jsx(AttachmentChips, { attachments, onRemove: (id) => onRemoveAttachment?.(id) }) }),
|
|
1051
|
+
hasAttachments && /* @__PURE__ */ jsx(
|
|
1052
|
+
"input",
|
|
727
1053
|
{
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
value: input,
|
|
1054
|
+
ref: fileInputRef,
|
|
1055
|
+
type: "file",
|
|
1056
|
+
multiple: true,
|
|
1057
|
+
accept: ".csv,.txt,.tsv,.pdf,text/plain,text/csv,application/pdf,image/jpeg,image/png,image/webp,image/gif",
|
|
1058
|
+
className: "hidden",
|
|
734
1059
|
onChange: (e) => {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
disabled: streaming,
|
|
741
|
-
autoFocus: true
|
|
1060
|
+
if (e.target.files?.length) {
|
|
1061
|
+
onAttach(e.target.files);
|
|
1062
|
+
e.target.value = "";
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
742
1065
|
}
|
|
743
1066
|
),
|
|
744
|
-
/* @__PURE__ */
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1067
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2 px-3 py-2", children: [
|
|
1068
|
+
/* @__PURE__ */ jsx(
|
|
1069
|
+
"textarea",
|
|
1070
|
+
{
|
|
1071
|
+
id: "wallavi-chat-input",
|
|
1072
|
+
ref: textareaRef,
|
|
1073
|
+
rows: 1,
|
|
1074
|
+
className: "flex-1 resize-none bg-transparent text-sm outline-none placeholder:text-muted-foreground/50 leading-relaxed max-h-32 overflow-y-auto py-0.5",
|
|
1075
|
+
placeholder: placeholder ?? "Send a message\u2026",
|
|
1076
|
+
value: input,
|
|
1077
|
+
onChange: (e) => {
|
|
1078
|
+
setInput(e.target.value);
|
|
1079
|
+
e.target.style.height = "auto";
|
|
1080
|
+
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
1081
|
+
},
|
|
1082
|
+
onKeyDown: handleKeyDown,
|
|
1083
|
+
onPaste: (e) => {
|
|
1084
|
+
if (!hasAttachments || !onAttach) return;
|
|
1085
|
+
const files = Array.from(e.clipboardData?.files ?? []).filter(
|
|
1086
|
+
(f) => f.type.startsWith("image/")
|
|
1087
|
+
);
|
|
1088
|
+
if (files.length > 0) {
|
|
1089
|
+
e.preventDefault();
|
|
1090
|
+
const dt = new DataTransfer();
|
|
1091
|
+
files.forEach((f) => dt.items.add(f));
|
|
1092
|
+
onAttach(dt.files);
|
|
1093
|
+
}
|
|
1094
|
+
},
|
|
1095
|
+
disabled: streaming || voiceState === "recording" || voiceState === "transcribing",
|
|
1096
|
+
autoFocus: true
|
|
1097
|
+
}
|
|
1098
|
+
),
|
|
1099
|
+
hasVoice && /* @__PURE__ */ jsx(
|
|
1100
|
+
"button",
|
|
1101
|
+
{
|
|
1102
|
+
type: "button",
|
|
1103
|
+
onClick: voiceState === "recording" ? onVoiceStop : onVoiceStart,
|
|
1104
|
+
disabled: streaming || voiceState === "transcribing",
|
|
1105
|
+
title: voiceState === "recording" ? "Stop recording" : "Record voice message",
|
|
1106
|
+
className: cn3(
|
|
1107
|
+
"h-7 w-7 shrink-0 rounded-xl flex items-center justify-center transition-all duration-200",
|
|
1108
|
+
voiceState === "recording" && "animate-pulse",
|
|
1109
|
+
voiceState === "error" ? "text-red-500 opacity-80" : "text-muted-foreground hover:text-foreground",
|
|
1110
|
+
(streaming || voiceState === "transcribing") && "opacity-40 pointer-events-none"
|
|
1111
|
+
),
|
|
1112
|
+
style: voiceState === "recording" ? { color: accentColor } : void 0,
|
|
1113
|
+
children: voiceState === "transcribing" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : voiceState === "recording" ? /* @__PURE__ */ jsx(Square, { className: "h-3.5 w-3.5 fill-current" }) : /* @__PURE__ */ jsx(Mic, { className: "h-3.5 w-3.5" })
|
|
1114
|
+
}
|
|
1115
|
+
),
|
|
1116
|
+
hasAttachments && /* @__PURE__ */ jsx(
|
|
1117
|
+
"button",
|
|
1118
|
+
{
|
|
1119
|
+
type: "button",
|
|
1120
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1121
|
+
disabled: streaming || isUploading,
|
|
1122
|
+
title: "Attach file (CSV, image\u2026)",
|
|
1123
|
+
className: cn3(
|
|
1124
|
+
"h-7 w-7 shrink-0 rounded-xl flex items-center justify-center transition-all duration-200",
|
|
1125
|
+
"text-muted-foreground hover:text-foreground",
|
|
1126
|
+
(streaming || isUploading) && "opacity-40 pointer-events-none"
|
|
1127
|
+
),
|
|
1128
|
+
children: isUploading ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" })
|
|
1129
|
+
}
|
|
1130
|
+
),
|
|
1131
|
+
/* @__PURE__ */ jsx(
|
|
1132
|
+
"button",
|
|
1133
|
+
{
|
|
1134
|
+
onClick: onSend,
|
|
1135
|
+
disabled: streaming || !hasText || voiceState === "recording" || voiceState === "transcribing",
|
|
1136
|
+
className: cn3(
|
|
1137
|
+
"h-7 w-7 shrink-0 rounded-xl flex items-center justify-center transition-all duration-200",
|
|
1138
|
+
hasText || streaming ? "opacity-100 shadow-sm" : "opacity-30"
|
|
1139
|
+
),
|
|
1140
|
+
style: hasText || streaming ? { backgroundColor: accentColor, color: getContrastColor(accentColor) } : { backgroundColor: "transparent", color: "currentColor" },
|
|
1141
|
+
children: streaming ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(ArrowUp, { className: "h-3.5 w-3.5" })
|
|
1142
|
+
}
|
|
1143
|
+
)
|
|
1144
|
+
] })
|
|
757
1145
|
] })
|
|
758
1146
|
] });
|
|
759
1147
|
}
|
|
@@ -779,6 +1167,9 @@ function ChatWidget({
|
|
|
779
1167
|
source = "playground",
|
|
780
1168
|
userContext,
|
|
781
1169
|
playgroundOverrides,
|
|
1170
|
+
enableVoice = false,
|
|
1171
|
+
voiceAutoSend = false,
|
|
1172
|
+
enableAttachments = false,
|
|
782
1173
|
className,
|
|
783
1174
|
onClose,
|
|
784
1175
|
onReset,
|
|
@@ -786,6 +1177,48 @@ function ChatWidget({
|
|
|
786
1177
|
expanded
|
|
787
1178
|
}) {
|
|
788
1179
|
const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
|
|
1180
|
+
const voice = useVoice({
|
|
1181
|
+
agentId,
|
|
1182
|
+
onTranscript: (text) => {
|
|
1183
|
+
if (voiceAutoSend) {
|
|
1184
|
+
void chat.send(text);
|
|
1185
|
+
} else {
|
|
1186
|
+
chat.setInput(text);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
const attachmentHook = useAttachments({ agentId });
|
|
1191
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
1192
|
+
const handleDragOver = useCallback((e) => {
|
|
1193
|
+
if (!enableAttachments) return;
|
|
1194
|
+
e.preventDefault();
|
|
1195
|
+
e.stopPropagation();
|
|
1196
|
+
if (e.dataTransfer.types.includes("Files")) setIsDragOver(true);
|
|
1197
|
+
}, [enableAttachments]);
|
|
1198
|
+
const handleDragLeave = useCallback((e) => {
|
|
1199
|
+
if (!enableAttachments) return;
|
|
1200
|
+
e.preventDefault();
|
|
1201
|
+
e.stopPropagation();
|
|
1202
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
1203
|
+
setIsDragOver(false);
|
|
1204
|
+
}
|
|
1205
|
+
}, [enableAttachments]);
|
|
1206
|
+
const handleDrop = useCallback((e) => {
|
|
1207
|
+
if (!enableAttachments) return;
|
|
1208
|
+
e.preventDefault();
|
|
1209
|
+
e.stopPropagation();
|
|
1210
|
+
setIsDragOver(false);
|
|
1211
|
+
const files = e.dataTransfer.files;
|
|
1212
|
+
if (files.length > 0) attachmentHook.attach(files);
|
|
1213
|
+
}, [enableAttachments, attachmentHook]);
|
|
1214
|
+
const handleSend = () => {
|
|
1215
|
+
const payloads = attachmentHook.readyPayloads;
|
|
1216
|
+
if (payloads.length > 0) {
|
|
1217
|
+
chat.queueAttachments(payloads);
|
|
1218
|
+
attachmentHook.clear();
|
|
1219
|
+
}
|
|
1220
|
+
void chat.send();
|
|
1221
|
+
};
|
|
789
1222
|
const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
|
|
790
1223
|
const title = displayName || agentName;
|
|
791
1224
|
const headerBg = userMessageColor;
|
|
@@ -798,11 +1231,21 @@ function ChatWidget({
|
|
|
798
1231
|
"div",
|
|
799
1232
|
{
|
|
800
1233
|
className: cn(
|
|
801
|
-
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
|
|
1234
|
+
"flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full relative",
|
|
1235
|
+
isDragOver && "ring-2 ring-inset ring-primary/60",
|
|
802
1236
|
className
|
|
803
1237
|
),
|
|
804
1238
|
style: { colorScheme: theme },
|
|
1239
|
+
onDragOver: handleDragOver,
|
|
1240
|
+
onDragEnter: handleDragOver,
|
|
1241
|
+
onDragLeave: handleDragLeave,
|
|
1242
|
+
onDrop: handleDrop,
|
|
805
1243
|
children: [
|
|
1244
|
+
isDragOver && enableAttachments && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-50 flex flex-col items-center justify-center gap-2 rounded-2xl bg-background/90 backdrop-blur-sm pointer-events-none", children: [
|
|
1245
|
+
/* @__PURE__ */ jsx(UploadCloud, { className: "h-8 w-8 text-primary/70" }),
|
|
1246
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground/70", children: "Drop files to attach" }),
|
|
1247
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "CSV, TXT, PDF, JPG, PNG, WebP" })
|
|
1248
|
+
] }),
|
|
806
1249
|
/* @__PURE__ */ jsx(
|
|
807
1250
|
ChatHeader,
|
|
808
1251
|
{
|
|
@@ -836,12 +1279,23 @@ function ChatWidget({
|
|
|
836
1279
|
{
|
|
837
1280
|
input: chat.input,
|
|
838
1281
|
setInput: chat.setInput,
|
|
839
|
-
onSend:
|
|
1282
|
+
onSend: handleSend,
|
|
840
1283
|
streaming: chat.streaming,
|
|
841
1284
|
placeholder: messagePlaceholder,
|
|
842
1285
|
accentColor: userMessageColor,
|
|
843
1286
|
canRegenerate: !!canRegenerate,
|
|
844
|
-
onRegenerate: () => void chat.regenerate()
|
|
1287
|
+
onRegenerate: () => void chat.regenerate(),
|
|
1288
|
+
...enableVoice && voice.isSupported ? {
|
|
1289
|
+
voiceState: voice.voiceState,
|
|
1290
|
+
onVoiceStart: () => void voice.start(),
|
|
1291
|
+
onVoiceStop: voice.stop
|
|
1292
|
+
} : {},
|
|
1293
|
+
...enableAttachments ? {
|
|
1294
|
+
attachments: attachmentHook.attachments,
|
|
1295
|
+
onAttach: attachmentHook.attach,
|
|
1296
|
+
onRemoveAttachment: attachmentHook.remove,
|
|
1297
|
+
isUploading: attachmentHook.isUploading
|
|
1298
|
+
} : {}
|
|
845
1299
|
}
|
|
846
1300
|
),
|
|
847
1301
|
watermark && /* @__PURE__ */ jsxs("footer", { className: "shrink-0 flex items-center justify-center gap-1.5 bg-muted/50 py-1.5 border-t", children: [
|
|
@@ -1107,4 +1561,4 @@ function BubbleWidget({
|
|
|
1107
1561
|
);
|
|
1108
1562
|
}
|
|
1109
1563
|
|
|
1110
|
-
export { BubbleWidget, ChatWidget, formatToolName, getContrastColor, useChat };
|
|
1564
|
+
export { BubbleWidget, ChatWidget, formatToolName, getContrastColor, useAttachments, useChat, useVoice };
|