@wallavi/widget 1.6.0 → 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 +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +128 -44
- package/dist/index.mjs +129 -45
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -32,6 +32,8 @@ type Message = {
|
|
|
32
32
|
id: string;
|
|
33
33
|
role: "user" | "assistant";
|
|
34
34
|
parts: MessagePart[];
|
|
35
|
+
/** Files the user attached to this message — stored for display in the chat thread. */
|
|
36
|
+
attachments?: AttachmentPayload[];
|
|
35
37
|
};
|
|
36
38
|
interface PageContext {
|
|
37
39
|
/** Current URL or path (e.g. "/dashboard/agent/abc/agent-studio") */
|
|
@@ -101,9 +103,9 @@ interface ChatWidgetConfig {
|
|
|
101
103
|
*/
|
|
102
104
|
voiceAutoSend?: boolean;
|
|
103
105
|
/**
|
|
104
|
-
* Show the paperclip button
|
|
105
|
-
*
|
|
106
|
-
*
|
|
106
|
+
* Show the paperclip button and enable drag-and-drop / clipboard-paste for file attachments.
|
|
107
|
+
* Supported types: CSV, TXT, PDF (text extracted as context), JPG, PNG, WebP, GIF (stored in Vercel Blob).
|
|
108
|
+
* Default: false.
|
|
107
109
|
*/
|
|
108
110
|
enableAttachments?: boolean;
|
|
109
111
|
}
|
|
@@ -125,6 +127,8 @@ interface AttachmentPayload {
|
|
|
125
127
|
contentType: AttachmentContentType;
|
|
126
128
|
textContent?: string;
|
|
127
129
|
base64?: string;
|
|
130
|
+
/** Persistent public URL — set by upload.ts when Vercel Blob is configured. */
|
|
131
|
+
url?: string;
|
|
128
132
|
rowCount?: number;
|
|
129
133
|
truncated?: boolean;
|
|
130
134
|
sizeBytes: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ type Message = {
|
|
|
32
32
|
id: string;
|
|
33
33
|
role: "user" | "assistant";
|
|
34
34
|
parts: MessagePart[];
|
|
35
|
+
/** Files the user attached to this message — stored for display in the chat thread. */
|
|
36
|
+
attachments?: AttachmentPayload[];
|
|
35
37
|
};
|
|
36
38
|
interface PageContext {
|
|
37
39
|
/** Current URL or path (e.g. "/dashboard/agent/abc/agent-studio") */
|
|
@@ -101,9 +103,9 @@ interface ChatWidgetConfig {
|
|
|
101
103
|
*/
|
|
102
104
|
voiceAutoSend?: boolean;
|
|
103
105
|
/**
|
|
104
|
-
* Show the paperclip button
|
|
105
|
-
*
|
|
106
|
-
*
|
|
106
|
+
* Show the paperclip button and enable drag-and-drop / clipboard-paste for file attachments.
|
|
107
|
+
* Supported types: CSV, TXT, PDF (text extracted as context), JPG, PNG, WebP, GIF (stored in Vercel Blob).
|
|
108
|
+
* Default: false.
|
|
107
109
|
*/
|
|
108
110
|
enableAttachments?: boolean;
|
|
109
111
|
}
|
|
@@ -125,6 +127,8 @@ interface AttachmentPayload {
|
|
|
125
127
|
contentType: AttachmentContentType;
|
|
126
128
|
textContent?: string;
|
|
127
129
|
base64?: string;
|
|
130
|
+
/** Persistent public URL — set by upload.ts when Vercel Blob is configured. */
|
|
131
|
+
url?: string;
|
|
128
132
|
rowCount?: number;
|
|
129
133
|
truncated?: boolean;
|
|
130
134
|
sizeBytes: number;
|
package/dist/index.js
CHANGED
|
@@ -254,17 +254,22 @@ function useChat({
|
|
|
254
254
|
const userInput = (text ?? input).trim();
|
|
255
255
|
if (!userInput || streaming) return;
|
|
256
256
|
setInput("");
|
|
257
|
+
const attachments = pendingAttachmentsRef.current.length > 0 ? [...pendingAttachmentsRef.current] : void 0;
|
|
258
|
+
pendingAttachmentsRef.current = [];
|
|
257
259
|
const userMsgId = newId();
|
|
258
260
|
setMessages((prev) => [
|
|
259
261
|
...prev,
|
|
260
|
-
{
|
|
262
|
+
{
|
|
263
|
+
id: userMsgId,
|
|
264
|
+
role: "user",
|
|
265
|
+
parts: [{ type: "text", text: userInput }],
|
|
266
|
+
...attachments ? { attachments } : {}
|
|
267
|
+
}
|
|
261
268
|
]);
|
|
262
269
|
setStreaming(true);
|
|
263
270
|
const assistantMsgId = newId();
|
|
264
271
|
streamingMsgIdRef.current = assistantMsgId;
|
|
265
272
|
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
266
|
-
const attachments = pendingAttachmentsRef.current.length > 0 ? [...pendingAttachmentsRef.current] : void 0;
|
|
267
|
-
pendingAttachmentsRef.current = [];
|
|
268
273
|
try {
|
|
269
274
|
await fetchAndStream({ input: userInput, msgId: assistantMsgId, attachments });
|
|
270
275
|
} catch {
|
|
@@ -606,10 +611,112 @@ function ChatHeader({
|
|
|
606
611
|
}
|
|
607
612
|
);
|
|
608
613
|
}
|
|
614
|
+
var cn2 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
615
|
+
function formatBytes(bytes) {
|
|
616
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
617
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
618
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
619
|
+
}
|
|
620
|
+
function ChipLeading({ a }) {
|
|
621
|
+
if (a.status === "uploading") return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 shrink-0 animate-spin" });
|
|
622
|
+
if (a.status === "error") return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-3 w-3 shrink-0" });
|
|
623
|
+
const thumbSrc = a.payload?.url ?? a.payload?.base64;
|
|
624
|
+
if (a.mimeType.startsWith("image/") && thumbSrc) {
|
|
625
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
626
|
+
"img",
|
|
627
|
+
{
|
|
628
|
+
src: thumbSrc,
|
|
629
|
+
alt: a.name,
|
|
630
|
+
className: "h-6 w-6 rounded object-cover shrink-0 border border-border/40"
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (a.mimeType.startsWith("image/")) return /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("span", { className: "text-[8px] font-semibold text-muted-foreground/50", children: "IMG" }) });
|
|
635
|
+
if (a.mimeType === "application/pdf") return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-3 w-3 shrink-0 text-red-400" });
|
|
636
|
+
if (a.mimeType.includes("csv") || a.mimeType.includes("spreadsheet") || a.name.endsWith(".csv")) {
|
|
637
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileSpreadsheet, { className: "h-3 w-3 shrink-0 text-emerald-500" });
|
|
638
|
+
}
|
|
639
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-3 w-3 shrink-0" });
|
|
640
|
+
}
|
|
641
|
+
function AttachmentChips({ attachments, onRemove }) {
|
|
642
|
+
if (attachments.length === 0) return null;
|
|
643
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pb-1.5", children: attachments.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
644
|
+
"div",
|
|
645
|
+
{
|
|
646
|
+
className: cn2(
|
|
647
|
+
"flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[11px] max-w-[200px]",
|
|
648
|
+
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"
|
|
649
|
+
),
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChipLeading, { a }),
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate leading-tight", children: a.status === "error" ? a.errorMessage ?? "Upload failed" : a.name }),
|
|
653
|
+
a.status === "ready" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground/50", children: formatBytes(a.sizeBytes) }),
|
|
654
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
655
|
+
"button",
|
|
656
|
+
{
|
|
657
|
+
type: "button",
|
|
658
|
+
onClick: () => onRemove(a.id),
|
|
659
|
+
className: "shrink-0 rounded-full p-0.5 hover:bg-foreground/10 transition-colors ml-0.5",
|
|
660
|
+
title: "Remove",
|
|
661
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-2.5 w-2.5" })
|
|
662
|
+
}
|
|
663
|
+
)
|
|
664
|
+
]
|
|
665
|
+
},
|
|
666
|
+
a.id
|
|
667
|
+
)) });
|
|
668
|
+
}
|
|
609
669
|
var Avatar2 = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
610
670
|
var AvatarImage2 = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
611
671
|
var AvatarFallback2 = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
|
|
612
672
|
var ReactMarkdown = ReactMarkdownLib__default.default;
|
|
673
|
+
function SentAttachments({ attachments, contrastColor }) {
|
|
674
|
+
const images = attachments.filter((a) => a.contentType === "image");
|
|
675
|
+
const files = attachments.filter((a) => a.contentType !== "image");
|
|
676
|
+
const isDark = contrastColor === "#ffffff" || contrastColor === "#fff";
|
|
677
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5 w-full", children: [
|
|
678
|
+
images.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex gap-1.5 flex-wrap", images.length === 1 && ""), children: images.map((img) => {
|
|
679
|
+
const src = img.url ?? img.base64;
|
|
680
|
+
return src ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
681
|
+
"img",
|
|
682
|
+
{
|
|
683
|
+
src,
|
|
684
|
+
alt: img.name,
|
|
685
|
+
className: cn(
|
|
686
|
+
"rounded-xl object-cover border",
|
|
687
|
+
images.length === 1 ? "w-full max-h-48" : "h-20 w-20",
|
|
688
|
+
isDark ? "border-white/20" : "border-black/10"
|
|
689
|
+
)
|
|
690
|
+
},
|
|
691
|
+
img.id
|
|
692
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
693
|
+
"div",
|
|
694
|
+
{
|
|
695
|
+
className: cn(
|
|
696
|
+
"h-20 w-20 rounded-xl flex items-center justify-center text-[10px] font-medium",
|
|
697
|
+
isDark ? "bg-white/10 text-white/60" : "bg-black/10 text-black/40"
|
|
698
|
+
),
|
|
699
|
+
children: "IMG"
|
|
700
|
+
},
|
|
701
|
+
img.id
|
|
702
|
+
);
|
|
703
|
+
}) }),
|
|
704
|
+
files.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
705
|
+
"div",
|
|
706
|
+
{
|
|
707
|
+
className: cn(
|
|
708
|
+
"flex items-center gap-1 rounded-lg px-2 py-1 text-[11px]",
|
|
709
|
+
isDark ? "bg-white/15 text-white/80" : "bg-black/10 text-black/60"
|
|
710
|
+
),
|
|
711
|
+
children: [
|
|
712
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[120px]", children: f.name }),
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60 shrink-0", children: formatBytes(f.sizeBytes) })
|
|
714
|
+
]
|
|
715
|
+
},
|
|
716
|
+
f.id
|
|
717
|
+
)) })
|
|
718
|
+
] });
|
|
719
|
+
}
|
|
613
720
|
function ThinkingDots() {
|
|
614
721
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
615
722
|
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `
|
|
@@ -800,12 +907,15 @@ function MessageBubble({
|
|
|
800
907
|
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
801
908
|
const contrastColor = getContrastColor(userColor);
|
|
802
909
|
if (isUser) {
|
|
803
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.
|
|
910
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
804
911
|
"div",
|
|
805
912
|
{
|
|
806
|
-
className: "max-w-[78%] rounded-2xl rounded-tr-sm px-4 py-2.5 text-sm leading-relaxed",
|
|
913
|
+
className: "max-w-[78%] rounded-2xl rounded-tr-sm px-4 py-2.5 text-sm leading-relaxed flex flex-col gap-2",
|
|
807
914
|
style: { backgroundColor: userColor, color: contrastColor },
|
|
808
|
-
children:
|
|
915
|
+
children: [
|
|
916
|
+
message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(SentAttachments, { attachments: message.attachments, contrastColor }),
|
|
917
|
+
textPart?.text && /* @__PURE__ */ jsxRuntime.jsx("span", { children: textPart.text })
|
|
918
|
+
]
|
|
809
919
|
}
|
|
810
920
|
) });
|
|
811
921
|
}
|
|
@@ -910,44 +1020,6 @@ function ChatMessages({
|
|
|
910
1020
|
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef })
|
|
911
1021
|
] });
|
|
912
1022
|
}
|
|
913
|
-
var cn2 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
914
|
-
function formatBytes(bytes) {
|
|
915
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
916
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
917
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
918
|
-
}
|
|
919
|
-
function FileIcon({ mimeType }) {
|
|
920
|
-
if (mimeType.startsWith("image/")) return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImageIcon, { className: "h-3 w-3 shrink-0" });
|
|
921
|
-
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-3 w-3 shrink-0" });
|
|
922
|
-
}
|
|
923
|
-
function AttachmentChips({ attachments, onRemove }) {
|
|
924
|
-
if (attachments.length === 0) return null;
|
|
925
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pb-1.5", children: attachments.map((a) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
926
|
-
"div",
|
|
927
|
-
{
|
|
928
|
-
className: cn2(
|
|
929
|
-
"flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[11px] max-w-[180px]",
|
|
930
|
-
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"
|
|
931
|
-
),
|
|
932
|
-
children: [
|
|
933
|
-
a.status === "uploading" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 shrink-0 animate-spin" }) : a.status === "error" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { mimeType: a.mimeType }),
|
|
934
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate leading-tight", children: a.status === "error" ? a.errorMessage ?? "Upload failed" : a.name }),
|
|
935
|
-
a.status === "ready" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground/60", children: formatBytes(a.sizeBytes) }),
|
|
936
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
937
|
-
"button",
|
|
938
|
-
{
|
|
939
|
-
type: "button",
|
|
940
|
-
onClick: () => onRemove(a.id),
|
|
941
|
-
className: "shrink-0 rounded-full p-0.5 hover:bg-foreground/10 transition-colors",
|
|
942
|
-
title: "Remove attachment",
|
|
943
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-2.5 w-2.5" })
|
|
944
|
-
}
|
|
945
|
-
)
|
|
946
|
-
]
|
|
947
|
-
},
|
|
948
|
-
a.id
|
|
949
|
-
)) });
|
|
950
|
-
}
|
|
951
1023
|
var cn3 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
952
1024
|
function ChatInput({
|
|
953
1025
|
input,
|
|
@@ -1034,6 +1106,18 @@ function ChatInput({
|
|
|
1034
1106
|
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
1035
1107
|
},
|
|
1036
1108
|
onKeyDown: handleKeyDown,
|
|
1109
|
+
onPaste: (e) => {
|
|
1110
|
+
if (!hasAttachments || !onAttach) return;
|
|
1111
|
+
const files = Array.from(e.clipboardData?.files ?? []).filter(
|
|
1112
|
+
(f) => f.type.startsWith("image/")
|
|
1113
|
+
);
|
|
1114
|
+
if (files.length > 0) {
|
|
1115
|
+
e.preventDefault();
|
|
1116
|
+
const dt = new DataTransfer();
|
|
1117
|
+
files.forEach((f) => dt.items.add(f));
|
|
1118
|
+
onAttach(dt.files);
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1037
1121
|
disabled: streaming || voiceState === "recording" || voiceState === "transcribing",
|
|
1038
1122
|
autoFocus: true
|
|
1039
1123
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useEffect, useState, useCallback, useMemo } from 'react';
|
|
2
|
-
import { UploadCloud, X, RotateCcw, Loader2, Square, Mic, Paperclip, ArrowUp,
|
|
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';
|
|
@@ -228,17 +228,22 @@ function useChat({
|
|
|
228
228
|
const userInput = (text ?? input).trim();
|
|
229
229
|
if (!userInput || streaming) return;
|
|
230
230
|
setInput("");
|
|
231
|
+
const attachments = pendingAttachmentsRef.current.length > 0 ? [...pendingAttachmentsRef.current] : void 0;
|
|
232
|
+
pendingAttachmentsRef.current = [];
|
|
231
233
|
const userMsgId = newId();
|
|
232
234
|
setMessages((prev) => [
|
|
233
235
|
...prev,
|
|
234
|
-
{
|
|
236
|
+
{
|
|
237
|
+
id: userMsgId,
|
|
238
|
+
role: "user",
|
|
239
|
+
parts: [{ type: "text", text: userInput }],
|
|
240
|
+
...attachments ? { attachments } : {}
|
|
241
|
+
}
|
|
235
242
|
]);
|
|
236
243
|
setStreaming(true);
|
|
237
244
|
const assistantMsgId = newId();
|
|
238
245
|
streamingMsgIdRef.current = assistantMsgId;
|
|
239
246
|
setMessages((prev) => [...prev, { id: assistantMsgId, role: "assistant", parts: [] }]);
|
|
240
|
-
const attachments = pendingAttachmentsRef.current.length > 0 ? [...pendingAttachmentsRef.current] : void 0;
|
|
241
|
-
pendingAttachmentsRef.current = [];
|
|
242
247
|
try {
|
|
243
248
|
await fetchAndStream({ input: userInput, msgId: assistantMsgId, attachments });
|
|
244
249
|
} catch {
|
|
@@ -580,10 +585,112 @@ function ChatHeader({
|
|
|
580
585
|
}
|
|
581
586
|
);
|
|
582
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
|
+
}
|
|
583
643
|
var Avatar2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
|
|
584
644
|
var AvatarImage2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
|
|
585
645
|
var AvatarFallback2 = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
|
|
586
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
|
+
}
|
|
587
694
|
function ThinkingDots() {
|
|
588
695
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
589
696
|
/* @__PURE__ */ jsx("style", { children: `
|
|
@@ -774,12 +881,15 @@ function MessageBubble({
|
|
|
774
881
|
const pickerParts = message.parts.filter((p) => p.type === "picker");
|
|
775
882
|
const contrastColor = getContrastColor(userColor);
|
|
776
883
|
if (isUser) {
|
|
777
|
-
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */
|
|
884
|
+
return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
778
885
|
"div",
|
|
779
886
|
{
|
|
780
|
-
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",
|
|
781
888
|
style: { backgroundColor: userColor, color: contrastColor },
|
|
782
|
-
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
|
+
]
|
|
783
893
|
}
|
|
784
894
|
) });
|
|
785
895
|
}
|
|
@@ -884,44 +994,6 @@ function ChatMessages({
|
|
|
884
994
|
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
885
995
|
] });
|
|
886
996
|
}
|
|
887
|
-
var cn2 = (...inputs) => twMerge(clsx(inputs));
|
|
888
|
-
function formatBytes(bytes) {
|
|
889
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
890
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
891
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
892
|
-
}
|
|
893
|
-
function FileIcon({ mimeType }) {
|
|
894
|
-
if (mimeType.startsWith("image/")) return /* @__PURE__ */ jsx(ImageIcon, { className: "h-3 w-3 shrink-0" });
|
|
895
|
-
return /* @__PURE__ */ jsx(FileText, { className: "h-3 w-3 shrink-0" });
|
|
896
|
-
}
|
|
897
|
-
function AttachmentChips({ attachments, onRemove }) {
|
|
898
|
-
if (attachments.length === 0) return null;
|
|
899
|
-
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pb-1.5", children: attachments.map((a) => /* @__PURE__ */ jsxs(
|
|
900
|
-
"div",
|
|
901
|
-
{
|
|
902
|
-
className: cn2(
|
|
903
|
-
"flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[11px] max-w-[180px]",
|
|
904
|
-
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"
|
|
905
|
-
),
|
|
906
|
-
children: [
|
|
907
|
-
a.status === "uploading" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 shrink-0 animate-spin" }) : a.status === "error" ? /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 shrink-0" }) : /* @__PURE__ */ jsx(FileIcon, { mimeType: a.mimeType }),
|
|
908
|
-
/* @__PURE__ */ jsx("span", { className: "truncate leading-tight", children: a.status === "error" ? a.errorMessage ?? "Upload failed" : a.name }),
|
|
909
|
-
a.status === "ready" && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground/60", children: formatBytes(a.sizeBytes) }),
|
|
910
|
-
/* @__PURE__ */ jsx(
|
|
911
|
-
"button",
|
|
912
|
-
{
|
|
913
|
-
type: "button",
|
|
914
|
-
onClick: () => onRemove(a.id),
|
|
915
|
-
className: "shrink-0 rounded-full p-0.5 hover:bg-foreground/10 transition-colors",
|
|
916
|
-
title: "Remove attachment",
|
|
917
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-2.5 w-2.5" })
|
|
918
|
-
}
|
|
919
|
-
)
|
|
920
|
-
]
|
|
921
|
-
},
|
|
922
|
-
a.id
|
|
923
|
-
)) });
|
|
924
|
-
}
|
|
925
997
|
var cn3 = (...inputs) => twMerge(clsx(inputs));
|
|
926
998
|
function ChatInput({
|
|
927
999
|
input,
|
|
@@ -1008,6 +1080,18 @@ function ChatInput({
|
|
|
1008
1080
|
e.target.style.height = `${Math.min(e.target.scrollHeight, 128)}px`;
|
|
1009
1081
|
},
|
|
1010
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
|
+
},
|
|
1011
1095
|
disabled: streaming || voiceState === "recording" || voiceState === "transcribing",
|
|
1012
1096
|
autoFocus: true
|
|
1013
1097
|
}
|