@mordn/chat-widget 0.6.2 → 0.7.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.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
 
4
4
  // src/ChatWidget.tsx
5
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef5, useState as useState6 } from "react";
5
+ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef5, useState as useState7 } from "react";
6
6
 
7
7
  // src/ui/button.tsx
8
8
  import { Slot } from "@radix-ui/react-slot";
@@ -191,10 +191,13 @@ Textarea.displayName = "Textarea";
191
191
 
192
192
  // src/components/prompt-input.tsx
193
193
  import {
194
+ FileIcon,
195
+ FileSpreadsheetIcon,
196
+ FileTextIcon,
194
197
  ImageIcon,
195
198
  Loader2Icon,
196
- PaperclipIcon,
197
199
  PlusIcon,
200
+ PresentationIcon,
198
201
  SendIcon,
199
202
  SquareIcon,
200
203
  XIcon
@@ -229,13 +232,22 @@ function PromptInputAttachment({
229
232
  ...props
230
233
  }) {
231
234
  const attachments = usePromptInputAttachments();
235
+ const isImage = data.mediaType?.startsWith("image/") && !!data.url;
232
236
  return /* @__PURE__ */ jsxs5(
233
237
  "div",
234
238
  {
235
- className: cn("group relative h-14 w-14 rounded-lg border", className),
239
+ className: cn(
240
+ "group relative rounded-lg border",
241
+ // Images keep the square thumbnail. Other files use a wider
242
+ // pill-shaped chip showing icon + filename — without it,
243
+ // non-image attachments rendered as identical paperclip
244
+ // squares with no way to tell them apart.
245
+ isImage ? "h-14 w-14" : "h-14 max-w-[200px]",
246
+ className
247
+ ),
236
248
  ...props,
237
249
  children: [
238
- data.mediaType?.startsWith("image/") && data.url ? /* @__PURE__ */ jsx8(
250
+ isImage ? /* @__PURE__ */ jsx8(
239
251
  "img",
240
252
  {
241
253
  alt: data.filename || "attachment",
@@ -244,7 +256,7 @@ function PromptInputAttachment({
244
256
  src: data.url,
245
257
  width: 56
246
258
  }
247
- ) : /* @__PURE__ */ jsx8("div", { className: "flex size-full items-center justify-center text-muted-foreground", children: /* @__PURE__ */ jsx8(PaperclipIcon, { className: "size-4" }) }),
259
+ ) : /* @__PURE__ */ jsx8(NonImageChip, { data }),
248
260
  /* @__PURE__ */ jsx8(
249
261
  Button,
250
262
  {
@@ -262,6 +274,34 @@ function PromptInputAttachment({
262
274
  data.id
263
275
  );
264
276
  }
277
+ function NonImageChip({ data }) {
278
+ const { Icon: Icon2, label } = describeFile(data);
279
+ return /* @__PURE__ */ jsxs5("div", { className: "flex size-full items-center gap-2 px-2.5", children: [
280
+ /* @__PURE__ */ jsx8(Icon2, { className: "size-5 flex-shrink-0 text-muted-foreground" }),
281
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex flex-col leading-tight", children: [
282
+ /* @__PURE__ */ jsx8("span", { className: "text-[12px] font-medium truncate", children: data.filename || "attachment" }),
283
+ /* @__PURE__ */ jsx8("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wide", children: label })
284
+ ] })
285
+ ] });
286
+ }
287
+ function describeFile(data) {
288
+ const mt = (data.mediaType || "").toLowerCase();
289
+ const ext = (data.filename || "").toLowerCase().split(".").pop() || "";
290
+ if (mt === "application/pdf" || ext === "pdf") return { Icon: FileTextIcon, label: "PDF" };
291
+ if (mt.includes("spreadsheet") || mt.includes("excel") || ext === "xlsx" || ext === "xls" || ext === "csv" || ext === "tsv") {
292
+ return { Icon: FileSpreadsheetIcon, label: ext.toUpperCase() || "Spreadsheet" };
293
+ }
294
+ if (mt.includes("presentation") || mt.includes("powerpoint") || ext === "pptx" || ext === "ppt") {
295
+ return { Icon: PresentationIcon, label: ext.toUpperCase() || "Slides" };
296
+ }
297
+ if (mt.includes("wordprocessing") || mt.includes("msword") || ext === "docx" || ext === "doc") {
298
+ return { Icon: FileTextIcon, label: ext.toUpperCase() || "Doc" };
299
+ }
300
+ if (mt.startsWith("text/") || ext === "txt" || ext === "md" || ext === "json") {
301
+ return { Icon: FileTextIcon, label: ext.toUpperCase() || "Text" };
302
+ }
303
+ return { Icon: FileIcon, label: ext.toUpperCase() || "File" };
304
+ }
265
305
  function PromptInputAttachments({
266
306
  className,
267
307
  children,
@@ -326,10 +366,18 @@ var PromptInput = ({
326
366
  if (!accept || accept.trim() === "") {
327
367
  return true;
328
368
  }
329
- if (accept.includes("image/*")) {
330
- return f.type.startsWith("image/");
331
- }
332
- return true;
369
+ const tokens = accept.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
370
+ const fileType = (f.type || "").toLowerCase();
371
+ const fileName = f.name.toLowerCase();
372
+ return tokens.some((token) => {
373
+ if (token === "*/*") return true;
374
+ if (token.startsWith(".")) return fileName.endsWith(token);
375
+ if (token.endsWith("/*")) {
376
+ const prefix = token.slice(0, -1);
377
+ return fileType.startsWith(prefix);
378
+ }
379
+ return fileType === token;
380
+ });
333
381
  },
334
382
  [accept]
335
383
  );
@@ -631,6 +679,8 @@ var PromptInputSubmit = ({
631
679
  variant = "default",
632
680
  size = "icon",
633
681
  status,
682
+ onStop,
683
+ onClick,
634
684
  children,
635
685
  ...props
636
686
  }) => {
@@ -642,13 +692,22 @@ var PromptInputSubmit = ({
642
692
  } else if (status === "error") {
643
693
  Icon2 = /* @__PURE__ */ jsx8(XIcon, { className: "size-4" });
644
694
  }
695
+ const isStopping = status === "streaming" && !!onStop;
645
696
  return /* @__PURE__ */ jsx8(
646
697
  Button,
647
698
  {
648
699
  className: cn("gap-1.5 rounded-lg", className),
649
700
  size,
650
- type: "submit",
701
+ type: isStopping ? "button" : "submit",
651
702
  variant,
703
+ onClick: (e) => {
704
+ if (isStopping) {
705
+ e.preventDefault();
706
+ onStop();
707
+ }
708
+ onClick?.(e);
709
+ },
710
+ "aria-label": isStopping ? "Stop generating" : void 0,
652
711
  ...props,
653
712
  children: children ?? Icon2
654
713
  }
@@ -656,62 +715,87 @@ var PromptInputSubmit = ({
656
715
  };
657
716
 
658
717
  // src/components/message-attachments.tsx
659
- import { PaperclipIcon as PaperclipIcon2 } from "lucide-react";
660
- import { jsx as jsx9 } from "react/jsx-runtime";
718
+ import {
719
+ FileIcon as FileIcon2,
720
+ FileSpreadsheetIcon as FileSpreadsheetIcon2,
721
+ FileTextIcon as FileTextIcon2,
722
+ PresentationIcon as PresentationIcon2
723
+ } from "lucide-react";
724
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
725
+ function openAttachment(attachment) {
726
+ if (attachment.url.startsWith("data:")) {
727
+ const w = window.open("", "_blank");
728
+ if (w) {
729
+ w.document.write(
730
+ `<html><head><title>${attachment.filename}</title></head><body style="margin:0;padding:20px;background:#f5f5f5;display:flex;justify-content:center;align-items:center;min-height:100vh;"><img src="${attachment.url}" alt="${attachment.filename}" style="max-width:100%;max-height:100%;object-fit:contain;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);" /></body></html>`
731
+ );
732
+ w.document.close();
733
+ }
734
+ return;
735
+ }
736
+ window.open(attachment.url, "_blank");
737
+ }
738
+ function describeFile2(att) {
739
+ const mt = (att.mediaType || "").toLowerCase();
740
+ const ext = (att.filename || "").toLowerCase().split(".").pop() || "";
741
+ if (mt === "application/pdf" || ext === "pdf") return { Icon: FileTextIcon2, label: "PDF" };
742
+ if (mt.includes("spreadsheet") || mt.includes("excel") || ext === "xlsx" || ext === "xls" || ext === "csv" || ext === "tsv") {
743
+ return { Icon: FileSpreadsheetIcon2, label: ext.toUpperCase() || "Spreadsheet" };
744
+ }
745
+ if (mt.includes("presentation") || mt.includes("powerpoint") || ext === "pptx" || ext === "ppt") {
746
+ return { Icon: PresentationIcon2, label: ext.toUpperCase() || "Slides" };
747
+ }
748
+ if (mt.includes("wordprocessing") || mt.includes("msword") || ext === "docx" || ext === "doc") {
749
+ return { Icon: FileTextIcon2, label: ext.toUpperCase() || "Doc" };
750
+ }
751
+ if (mt.startsWith("text/") || ext === "txt" || ext === "md" || ext === "json") {
752
+ return { Icon: FileTextIcon2, label: ext.toUpperCase() || "Text" };
753
+ }
754
+ return { Icon: FileIcon2, label: ext.toUpperCase() || "File" };
755
+ }
661
756
  function MessageAttachments({ attachments, className }) {
662
757
  if (!attachments || attachments.length === 0) {
663
758
  return null;
664
759
  }
665
- const handleAttachmentClick = (attachment) => {
666
- if (attachment.url.startsWith("data:")) {
667
- const newWindow = window.open("", "_blank");
668
- if (newWindow) {
669
- newWindow.document.write(`
670
- <html>
671
- <head><title>${attachment.filename}</title></head>
672
- <body style="margin:0; padding:20px; background:#f5f5f5; display:flex; justify-content:center; align-items:center; min-height:100vh;">
673
- <img src="${attachment.url}" alt="${attachment.filename}" style="max-width:100%; max-height:100%; object-fit:contain; border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,0.15);" />
674
- </body>
675
- </html>
676
- `);
677
- newWindow.document.close();
678
- }
679
- } else if (attachment.url.startsWith("blob:")) {
680
- window.open(attachment.url, "_blank");
681
- } else if (attachment.url.startsWith("http")) {
682
- window.open(attachment.url, "_blank");
683
- } else {
684
- window.open(attachment.url, "_blank");
685
- }
686
- };
687
- return /* @__PURE__ */ jsx9("div", { className: cn("flex flex-wrap gap-2", className), children: attachments.map((attachment, index) => /* @__PURE__ */ jsx9(
688
- "div",
689
- {
690
- className: "group relative h-14 w-14 rounded-lg",
691
- children: attachment.mediaType.startsWith("image/") ? /* @__PURE__ */ jsx9(
760
+ return /* @__PURE__ */ jsx9("div", { className: cn("flex flex-wrap gap-2", className), children: attachments.map((attachment, index) => {
761
+ const isImage = attachment.mediaType.startsWith("image/");
762
+ if (isImage) {
763
+ return /* @__PURE__ */ jsx9("div", { className: "group relative h-14 w-14 rounded-lg", children: /* @__PURE__ */ jsx9(
692
764
  "img",
693
765
  {
694
766
  src: attachment.url,
695
767
  alt: attachment.filename,
696
768
  className: "size-full rounded-lg object-cover cursor-pointer hover:opacity-80 transition-opacity",
697
- onClick: () => handleAttachmentClick(attachment)
698
- }
699
- ) : /* @__PURE__ */ jsx9(
700
- "div",
701
- {
702
- className: "flex size-full items-center justify-center text-muted-foreground cursor-pointer hover:bg-secondary/50 rounded-lg transition-colors",
703
- onClick: () => handleAttachmentClick(attachment),
704
- children: /* @__PURE__ */ jsx9(PaperclipIcon2, { className: "size-4" })
769
+ onClick: () => openAttachment(attachment)
705
770
  }
706
- )
707
- },
708
- index
709
- )) });
771
+ ) }, index);
772
+ }
773
+ const { Icon: Icon2, label } = describeFile2(attachment);
774
+ return /* @__PURE__ */ jsxs6(
775
+ "button",
776
+ {
777
+ type: "button",
778
+ onClick: () => openAttachment(attachment),
779
+ className: cn(
780
+ "group flex items-center gap-2 px-2.5 h-14 rounded-lg border max-w-[220px]",
781
+ "hover:bg-[hsl(var(--chat-text)/0.04)] transition-colors text-left cursor-pointer"
782
+ ),
783
+ children: [
784
+ /* @__PURE__ */ jsx9(Icon2, { className: "size-5 flex-shrink-0 text-muted-foreground" }),
785
+ /* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex flex-col leading-tight", children: [
786
+ /* @__PURE__ */ jsx9("span", { className: "text-[12px] font-medium truncate", children: attachment.filename }),
787
+ /* @__PURE__ */ jsx9("span", { className: "text-[10px] text-muted-foreground uppercase tracking-wide", children: label })
788
+ ] })
789
+ ]
790
+ },
791
+ index
792
+ );
793
+ }) });
710
794
  }
711
795
 
712
796
  // src/components/input-plugin-popover.tsx
713
797
  import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
714
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
798
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
715
799
  var FETCH_DEBOUNCE_MS = 120;
716
800
  function useInputPlugins({ textareaRef, value, setValue, plugins }) {
717
801
  const [active, setActive] = useState2(null);
@@ -720,6 +804,7 @@ function useInputPlugins({ textareaRef, value, setValue, plugins }) {
720
804
  const [loading, setLoading] = useState2(false);
721
805
  const debounceRef = useRef2(null);
722
806
  const requestIdRef = useRef2(0);
807
+ const sessionValidatedRef = useRef2(false);
723
808
  const pluginsByTrigger = useMemo2(() => {
724
809
  const map = /* @__PURE__ */ new Map();
725
810
  for (const p of plugins ?? []) {
@@ -736,9 +821,14 @@ function useInputPlugins({ textareaRef, value, setValue, plugins }) {
736
821
  return value.slice(active.triggerIndex + 1, cursor);
737
822
  }, [active, value, textareaRef]);
738
823
  useEffect2(() => {
739
- if (!active) return;
740
- const ta = textareaRef.current;
741
- if (!ta) return;
824
+ if (!active) {
825
+ sessionValidatedRef.current = false;
826
+ return;
827
+ }
828
+ if (value[active.triggerIndex] === active.plugin.trigger) {
829
+ sessionValidatedRef.current = true;
830
+ }
831
+ if (!sessionValidatedRef.current) return;
742
832
  if (value[active.triggerIndex] !== active.plugin.trigger) {
743
833
  setActive(null);
744
834
  return;
@@ -746,7 +836,7 @@ function useInputPlugins({ textareaRef, value, setValue, plugins }) {
746
836
  if (/\s/.test(query)) {
747
837
  setActive(null);
748
838
  }
749
- }, [active, value, query, textareaRef]);
839
+ }, [active, value, query]);
750
840
  useEffect2(() => {
751
841
  if (!active) {
752
842
  setItems([]);
@@ -754,13 +844,20 @@ function useInputPlugins({ textareaRef, value, setValue, plugins }) {
754
844
  return;
755
845
  }
756
846
  if (debounceRef.current) clearTimeout(debounceRef.current);
757
- setLoading(true);
758
847
  const reqId = ++requestIdRef.current;
848
+ const result = active.plugin.fetch(query);
849
+ if (Array.isArray(result)) {
850
+ setItems(result);
851
+ setHighlight(0);
852
+ setLoading(false);
853
+ return;
854
+ }
855
+ setLoading(true);
759
856
  debounceRef.current = setTimeout(async () => {
760
857
  try {
761
- const result = await active.plugin.fetch(query);
858
+ const items2 = await result;
762
859
  if (reqId !== requestIdRef.current) return;
763
- setItems(result);
860
+ setItems(items2);
764
861
  setHighlight(0);
765
862
  } catch (err) {
766
863
  console.error("[input-plugin] fetch failed:", err);
@@ -841,14 +938,8 @@ function useInputPlugins({ textareaRef, value, setValue, plugins }) {
841
938
  },
842
939
  [active, items, highlight, selectItem, close, pluginsByTrigger, value]
843
940
  );
844
- useEffect2(() => {
845
- if (!active) return;
846
- if (value[active.triggerIndex] !== active.plugin.trigger) {
847
- close();
848
- }
849
- }, [value, active, close]);
850
- const popover = active && (loading || items.length > 0 || active.plugin.emptyText) ? /* @__PURE__ */ jsx10(
851
- PluginPopover,
941
+ const panel = active ? /* @__PURE__ */ jsx10(
942
+ PluginPanel,
852
943
  {
853
944
  plugin: active.plugin,
854
945
  items,
@@ -858,51 +949,257 @@ function useInputPlugins({ textareaRef, value, setValue, plugins }) {
858
949
  onSelect: selectItem
859
950
  }
860
951
  ) : null;
861
- return { onKeyDown, popover, isOpen: !!active };
952
+ return { onKeyDown, panel, isOpen: !!active };
862
953
  }
863
- function PluginPopover({ plugin, items, loading, highlight, onHover, onSelect }) {
864
- return /* @__PURE__ */ jsxs6(
954
+ function PluginPanel({ plugin, items, loading, highlight, onHover, onSelect }) {
955
+ const viewportRef = useRef2(null);
956
+ const itemRefs = useRef2([]);
957
+ useEffect2(() => {
958
+ const btn = itemRefs.current[highlight];
959
+ const viewport = viewportRef.current;
960
+ if (!btn || !viewport) return;
961
+ const btnRect = btn.getBoundingClientRect();
962
+ const viewRect = viewport.getBoundingClientRect();
963
+ if (btnRect.top < viewRect.top) {
964
+ viewport.scrollTop -= viewRect.top - btnRect.top;
965
+ } else if (btnRect.bottom > viewRect.bottom) {
966
+ viewport.scrollTop += btnRect.bottom - viewRect.bottom;
967
+ }
968
+ }, [highlight]);
969
+ return /* @__PURE__ */ jsxs7(
865
970
  "div",
866
971
  {
867
972
  role: "listbox",
868
- className: cn(
869
- "absolute bottom-full left-0 right-0 mb-2 z-30",
870
- "rounded-md border border-border bg-popover text-popover-foreground shadow-md",
871
- "max-h-64 overflow-y-auto"
872
- ),
973
+ className: "rounded-t-xl bg-[hsl(var(--chat-background))] overflow-hidden mx-auto",
873
974
  onMouseDown: (e) => e.preventDefault(),
975
+ style: {
976
+ width: "96%",
977
+ borderTop: "1px solid var(--chat-divider)",
978
+ borderLeft: "1px solid var(--chat-divider)",
979
+ borderRight: "1px solid var(--chat-divider)",
980
+ // Pull down 1px so our bottom edge overlaps the form's top
981
+ // border, removing the visible seam between the two surfaces.
982
+ marginBottom: -1
983
+ },
874
984
  children: [
875
- plugin.heading && /* @__PURE__ */ jsx10("div", { className: "px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border", children: plugin.heading }),
876
- loading && items.length === 0 && /* @__PURE__ */ jsx10("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: "Loading\u2026" }),
877
- !loading && items.length === 0 && /* @__PURE__ */ jsx10("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: plugin.emptyText ?? "No results" }),
878
- items.map((item, idx) => /* @__PURE__ */ jsxs6(
985
+ plugin.heading && /* @__PURE__ */ jsx10(
986
+ "div",
987
+ {
988
+ className: "px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wide",
989
+ style: {
990
+ color: "hsl(var(--chat-text)/0.5)",
991
+ borderBottom: "1px solid var(--chat-divider)"
992
+ },
993
+ children: plugin.heading
994
+ }
995
+ ),
996
+ loading && items.length === 0 && /* @__PURE__ */ jsx10(
997
+ "div",
998
+ {
999
+ className: "px-3 py-2 text-[13px]",
1000
+ style: { color: "hsl(var(--chat-text)/0.5)" },
1001
+ children: "Loading\u2026"
1002
+ }
1003
+ ),
1004
+ !loading && items.length === 0 && /* @__PURE__ */ jsx10(
1005
+ "div",
1006
+ {
1007
+ className: "px-3 py-2 text-[13px]",
1008
+ style: { color: "hsl(var(--chat-text)/0.5)" },
1009
+ children: plugin.emptyText ?? "No results"
1010
+ }
1011
+ ),
1012
+ items.length > 0 && /* @__PURE__ */ jsx10(
1013
+ "div",
1014
+ {
1015
+ ref: viewportRef,
1016
+ className: "max-h-[200px] overflow-y-auto",
1017
+ children: /* @__PURE__ */ jsx10("div", { className: "py-1", children: items.map((item, idx) => /* @__PURE__ */ jsxs7("div", { children: [
1018
+ /* @__PURE__ */ jsxs7(
1019
+ "button",
1020
+ {
1021
+ ref: (el) => {
1022
+ itemRefs.current[idx] = el;
1023
+ },
1024
+ type: "button",
1025
+ role: "option",
1026
+ "aria-selected": idx === highlight,
1027
+ onMouseEnter: () => onHover(idx),
1028
+ onClick: () => onSelect(item),
1029
+ className: cn(
1030
+ "w-full text-left px-3 py-2",
1031
+ "flex items-center justify-between gap-3",
1032
+ "transition-colors duration-150 ease-out",
1033
+ "cursor-pointer"
1034
+ ),
1035
+ style: {
1036
+ backgroundColor: idx === highlight ? "hsl(var(--chat-text)/0.06)" : "transparent"
1037
+ },
1038
+ children: [
1039
+ /* @__PURE__ */ jsx10(
1040
+ "span",
1041
+ {
1042
+ className: "text-[13px] truncate",
1043
+ style: { color: "hsl(var(--chat-text)/0.85)" },
1044
+ children: item.label
1045
+ }
1046
+ ),
1047
+ item.sublabel && /* @__PURE__ */ jsx10(
1048
+ "span",
1049
+ {
1050
+ className: "text-[11px] flex-shrink-0",
1051
+ style: { color: "hsl(var(--chat-text)/0.4)" },
1052
+ children: item.sublabel
1053
+ }
1054
+ )
1055
+ ]
1056
+ }
1057
+ ),
1058
+ idx < items.length - 1 && /* @__PURE__ */ jsx10(
1059
+ "div",
1060
+ {
1061
+ className: "h-px mx-3",
1062
+ style: { backgroundColor: "var(--chat-divider)" }
1063
+ }
1064
+ )
1065
+ ] }, item.id)) })
1066
+ }
1067
+ )
1068
+ ]
1069
+ }
1070
+ );
1071
+ }
1072
+
1073
+ // src/components/chat-error-banner.tsx
1074
+ import { AlertTriangleIcon, XIcon as XIcon2 } from "lucide-react";
1075
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1076
+ function ChatErrorBanner({
1077
+ error,
1078
+ canRetry = true,
1079
+ onRetry,
1080
+ onDismiss
1081
+ }) {
1082
+ if (!error) return null;
1083
+ const friendly = friendlyErrorMessage(error);
1084
+ return /* @__PURE__ */ jsxs8(
1085
+ "div",
1086
+ {
1087
+ role: "alert",
1088
+ className: "flex items-start gap-2 px-3 py-2 mb-2 rounded-lg text-[13px]",
1089
+ style: {
1090
+ backgroundColor: "hsl(var(--chat-text)/0.04)",
1091
+ border: "1px solid var(--chat-divider)"
1092
+ },
1093
+ title: error.message,
1094
+ children: [
1095
+ /* @__PURE__ */ jsx11(
1096
+ AlertTriangleIcon,
1097
+ {
1098
+ className: "size-4 mt-0.5 flex-shrink-0",
1099
+ style: { color: "hsl(var(--chat-text)/0.6)" }
1100
+ }
1101
+ ),
1102
+ /* @__PURE__ */ jsx11("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx11("span", { style: { color: "hsl(var(--chat-text)/0.85)" }, children: friendly }) }),
1103
+ canRetry && onRetry && /* @__PURE__ */ jsx11(
879
1104
  "button",
880
1105
  {
881
1106
  type: "button",
882
- role: "option",
883
- "aria-selected": idx === highlight,
884
- onMouseEnter: () => onHover(idx),
885
- onClick: () => onSelect(item),
886
- className: cn(
887
- "w-full text-left px-3 py-2 text-sm transition-colors",
888
- "flex flex-col gap-0.5",
889
- idx === highlight ? "bg-accent text-accent-foreground" : "hover:bg-muted"
890
- ),
891
- children: [
892
- /* @__PURE__ */ jsx10("span", { className: "font-medium leading-tight", children: item.label }),
893
- item.sublabel && /* @__PURE__ */ jsx10("span", { className: "text-[11px] text-muted-foreground leading-tight", children: item.sublabel })
894
- ]
895
- },
896
- item.id
897
- ))
1107
+ onClick: onRetry,
1108
+ className: "text-[12px] font-medium underline-offset-2 hover:underline",
1109
+ style: { color: "hsl(var(--chat-text)/0.85)" },
1110
+ children: "Try again"
1111
+ }
1112
+ ),
1113
+ onDismiss && /* @__PURE__ */ jsx11(
1114
+ "button",
1115
+ {
1116
+ type: "button",
1117
+ onClick: onDismiss,
1118
+ "aria-label": "Dismiss",
1119
+ className: "flex-shrink-0 -mr-1 -mt-0.5 p-1 rounded hover:bg-[hsl(var(--chat-text)/0.06)] transition-colors",
1120
+ style: { color: "hsl(var(--chat-text)/0.5)" },
1121
+ children: /* @__PURE__ */ jsx11(XIcon2, { className: "size-3.5" })
1122
+ }
1123
+ )
1124
+ ]
1125
+ }
1126
+ );
1127
+ }
1128
+ function friendlyErrorMessage(error) {
1129
+ const raw = error.message ?? "";
1130
+ if (/abort/i.test(raw)) return "Stopped.";
1131
+ if (/network|fetch|disconnect|ECONN/i.test(raw)) {
1132
+ return "Connection issue. Check your network and try again.";
1133
+ }
1134
+ if (/rate.?limit|429/i.test(raw)) {
1135
+ return "You're sending messages too fast. Wait a moment and try again.";
1136
+ }
1137
+ if (/timeout/i.test(raw)) return "The response took too long.";
1138
+ return "Something went wrong while generating the response.";
1139
+ }
1140
+
1141
+ // src/components/message-actions.tsx
1142
+ import { useState as useState3 } from "react";
1143
+ import { CheckIcon as CheckIcon3, CopyIcon, RotateCcwIcon } from "lucide-react";
1144
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1145
+ function MessageActions({
1146
+ text,
1147
+ onRegenerate,
1148
+ regenerateDisabled,
1149
+ className
1150
+ }) {
1151
+ const [copied, setCopied] = useState3(false);
1152
+ const handleCopy = async () => {
1153
+ if (!text) return;
1154
+ try {
1155
+ await navigator.clipboard.writeText(text);
1156
+ setCopied(true);
1157
+ setTimeout(() => setCopied(false), 1500);
1158
+ } catch {
1159
+ }
1160
+ };
1161
+ return /* @__PURE__ */ jsxs9(
1162
+ "div",
1163
+ {
1164
+ className: cn(
1165
+ "flex items-center gap-1 mt-1.5 -ml-1.5",
1166
+ // Pulled left a touch so the icons line up with the message
1167
+ // text edge instead of its padding box.
1168
+ className
1169
+ ),
1170
+ children: [
1171
+ /* @__PURE__ */ jsx12(ActionButton, { onClick: handleCopy, ariaLabel: "Copy message", children: copied ? /* @__PURE__ */ jsx12(CheckIcon3, { className: "size-3.5" }) : /* @__PURE__ */ jsx12(CopyIcon, { className: "size-3.5" }) }),
1172
+ onRegenerate && /* @__PURE__ */ jsx12(
1173
+ ActionButton,
1174
+ {
1175
+ onClick: onRegenerate,
1176
+ disabled: regenerateDisabled,
1177
+ ariaLabel: "Regenerate response",
1178
+ children: /* @__PURE__ */ jsx12(RotateCcwIcon, { className: "size-3.5" })
1179
+ }
1180
+ )
898
1181
  ]
899
1182
  }
900
1183
  );
901
1184
  }
1185
+ function ActionButton({ onClick, disabled, ariaLabel, children }) {
1186
+ return /* @__PURE__ */ jsx12(
1187
+ "button",
1188
+ {
1189
+ type: "button",
1190
+ onClick,
1191
+ disabled,
1192
+ "aria-label": ariaLabel,
1193
+ className: "p-1.5 rounded-md transition-colors hover:bg-[hsl(var(--chat-text)/0.06)] disabled:opacity-40 disabled:cursor-not-allowed",
1194
+ style: { color: "hsl(var(--chat-text)/0.55)" },
1195
+ children
1196
+ }
1197
+ );
1198
+ }
902
1199
 
903
1200
  // src/components/interface.tsx
904
- import { useState as useState5, useEffect as useEffect5, useRef as useRef4, useMemo as useMemo3, useCallback as useCallback4 } from "react";
905
- import { HistoryIcon, MessageSquareIcon, SearchIcon, ChevronRightIcon as ChevronRightIcon2, PlusIcon as PlusIcon2, XIcon as XIcon2 } from "lucide-react";
1201
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useMemo as useMemo3, useCallback as useCallback4 } from "react";
1202
+ import { HistoryIcon, MessageSquareIcon, SearchIcon, ChevronRightIcon as ChevronRightIcon2, PlusIcon as PlusIcon2, XIcon as XIcon3 } from "lucide-react";
906
1203
  import { Fragment as Fragment5 } from "react";
907
1204
  import { useChat } from "@ai-sdk/react";
908
1205
  import { DefaultChatTransport } from "ai";
@@ -946,11 +1243,11 @@ function useCodeBlockAutoScroll(isStreaming) {
946
1243
  }
947
1244
 
948
1245
  // src/components/response.tsx
949
- import { jsx as jsx11 } from "react/jsx-runtime";
1246
+ import { jsx as jsx13 } from "react/jsx-runtime";
950
1247
  var Response = memo(
951
1248
  ({ className, isStreaming = false, ...props }) => {
952
1249
  const containerRef = useCodeBlockAutoScroll(isStreaming);
953
- return /* @__PURE__ */ jsx11("div", { ref: containerRef, children: /* @__PURE__ */ jsx11(
1250
+ return /* @__PURE__ */ jsx13("div", { ref: containerRef, children: /* @__PURE__ */ jsx13(
954
1251
  Streamdown,
955
1252
  {
956
1253
  className: cn(
@@ -967,16 +1264,16 @@ Response.displayName = "Response";
967
1264
 
968
1265
  // src/ui/collapsible.tsx
969
1266
  import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
970
- import { jsx as jsx12 } from "react/jsx-runtime";
1267
+ import { jsx as jsx14 } from "react/jsx-runtime";
971
1268
  function Collapsible({
972
1269
  ...props
973
1270
  }) {
974
- return /* @__PURE__ */ jsx12(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
1271
+ return /* @__PURE__ */ jsx14(CollapsiblePrimitive.Root, { "data-slot": "collapsible", ...props });
975
1272
  }
976
1273
  function CollapsibleTrigger2({
977
1274
  ...props
978
1275
  }) {
979
- return /* @__PURE__ */ jsx12(
1276
+ return /* @__PURE__ */ jsx14(
980
1277
  CollapsiblePrimitive.CollapsibleTrigger,
981
1278
  {
982
1279
  "data-slot": "collapsible-trigger",
@@ -987,7 +1284,7 @@ function CollapsibleTrigger2({
987
1284
  function CollapsibleContent2({
988
1285
  ...props
989
1286
  }) {
990
- return /* @__PURE__ */ jsx12(
1287
+ return /* @__PURE__ */ jsx14(
991
1288
  CollapsiblePrimitive.CollapsibleContent,
992
1289
  {
993
1290
  "data-slot": "collapsible-content",
@@ -998,8 +1295,8 @@ function CollapsibleContent2({
998
1295
 
999
1296
  // src/components/sources.tsx
1000
1297
  import { BookIcon, ChevronDownIcon as ChevronDownIcon2 } from "lucide-react";
1001
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1002
- var Sources = ({ className, ...props }) => /* @__PURE__ */ jsx13(
1298
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1299
+ var Sources = ({ className, ...props }) => /* @__PURE__ */ jsx15(
1003
1300
  Collapsible,
1004
1301
  {
1005
1302
  className: cn("not-prose mb-4 text-primary text-xs", className),
@@ -1011,25 +1308,25 @@ var SourcesTrigger = ({
1011
1308
  count,
1012
1309
  children,
1013
1310
  ...props
1014
- }) => /* @__PURE__ */ jsx13(
1311
+ }) => /* @__PURE__ */ jsx15(
1015
1312
  CollapsibleTrigger2,
1016
1313
  {
1017
1314
  className: cn("flex items-center gap-2", className),
1018
1315
  ...props,
1019
- children: children ?? /* @__PURE__ */ jsxs7(Fragment3, { children: [
1020
- /* @__PURE__ */ jsxs7("p", { className: "font-medium", children: [
1316
+ children: children ?? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1317
+ /* @__PURE__ */ jsxs10("p", { className: "font-medium", children: [
1021
1318
  "Used ",
1022
1319
  count,
1023
1320
  " sources"
1024
1321
  ] }),
1025
- /* @__PURE__ */ jsx13(ChevronDownIcon2, { className: "h-4 w-4" })
1322
+ /* @__PURE__ */ jsx15(ChevronDownIcon2, { className: "h-4 w-4" })
1026
1323
  ] })
1027
1324
  }
1028
1325
  );
1029
1326
  var SourcesContent = ({
1030
1327
  className,
1031
1328
  ...props
1032
- }) => /* @__PURE__ */ jsx13(
1329
+ }) => /* @__PURE__ */ jsx15(
1033
1330
  CollapsibleContent2,
1034
1331
  {
1035
1332
  className: cn(
@@ -1040,7 +1337,7 @@ var SourcesContent = ({
1040
1337
  ...props
1041
1338
  }
1042
1339
  );
1043
- var Source = ({ href, title, children, ...props }) => /* @__PURE__ */ jsx13(
1340
+ var Source = ({ href, title, children, ...props }) => /* @__PURE__ */ jsx15(
1044
1341
  "a",
1045
1342
  {
1046
1343
  className: "flex items-center gap-2",
@@ -1048,9 +1345,9 @@ var Source = ({ href, title, children, ...props }) => /* @__PURE__ */ jsx13(
1048
1345
  rel: "noreferrer",
1049
1346
  target: "_blank",
1050
1347
  ...props,
1051
- children: children ?? /* @__PURE__ */ jsxs7(Fragment3, { children: [
1052
- /* @__PURE__ */ jsx13(BookIcon, { className: "h-4 w-4" }),
1053
- /* @__PURE__ */ jsx13("span", { className: "block font-medium", children: title })
1348
+ children: children ?? /* @__PURE__ */ jsxs10(Fragment3, { children: [
1349
+ /* @__PURE__ */ jsx15(BookIcon, { className: "h-4 w-4" }),
1350
+ /* @__PURE__ */ jsx15("span", { className: "block font-medium", children: title })
1054
1351
  ] })
1055
1352
  }
1056
1353
  );
@@ -1058,8 +1355,8 @@ var Source = ({ href, title, children, ...props }) => /* @__PURE__ */ jsx13(
1058
1355
  // src/components/reasoning.tsx
1059
1356
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
1060
1357
  import { BrainIcon, ChevronDownIcon as ChevronDownIcon3 } from "lucide-react";
1061
- import { createContext as createContext2, memo as memo2, useContext as useContext2, useEffect as useEffect4, useState as useState3 } from "react";
1062
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1358
+ import { createContext as createContext2, memo as memo2, useContext as useContext2, useEffect as useEffect4, useState as useState4 } from "react";
1359
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1063
1360
  var ReasoningContext = createContext2(null);
1064
1361
  var useReasoning = () => {
1065
1362
  const context = useContext2(ReasoningContext);
@@ -1090,8 +1387,8 @@ var Reasoning = memo2(
1090
1387
  prop: durationProp,
1091
1388
  defaultProp: void 0
1092
1389
  });
1093
- const [hasAutoClosed, setHasAutoClosed] = useState3(false);
1094
- const [startTime, setStartTime] = useState3(null);
1390
+ const [hasAutoClosed, setHasAutoClosed] = useState4(false);
1391
+ const [startTime, setStartTime] = useState4(null);
1095
1392
  useEffect4(() => {
1096
1393
  if (isStreaming) {
1097
1394
  if (startTime === null) {
@@ -1114,11 +1411,11 @@ var Reasoning = memo2(
1114
1411
  const handleOpenChange = (newOpen) => {
1115
1412
  setIsOpen(newOpen);
1116
1413
  };
1117
- return /* @__PURE__ */ jsx14(
1414
+ return /* @__PURE__ */ jsx16(
1118
1415
  ReasoningContext.Provider,
1119
1416
  {
1120
1417
  value: { isStreaming, isOpen, setIsOpen, duration },
1121
- children: /* @__PURE__ */ jsx14(
1418
+ children: /* @__PURE__ */ jsx16(
1122
1419
  Collapsible,
1123
1420
  {
1124
1421
  className: cn("not-prose", className),
@@ -1134,15 +1431,15 @@ var Reasoning = memo2(
1134
1431
  );
1135
1432
  var getThinkingMessage = (isStreaming, duration) => {
1136
1433
  if (isStreaming) {
1137
- return /* @__PURE__ */ jsx14(Fragment4, { children: "Thinking..." });
1434
+ return /* @__PURE__ */ jsx16(Fragment4, { children: "Thinking..." });
1138
1435
  }
1139
1436
  if (duration === void 0) {
1140
- return /* @__PURE__ */ jsx14(Fragment4, { children: "Thought process" });
1437
+ return /* @__PURE__ */ jsx16(Fragment4, { children: "Thought process" });
1141
1438
  }
1142
1439
  if (duration === 0) {
1143
- return /* @__PURE__ */ jsx14(Fragment4, { children: "Thought process" });
1440
+ return /* @__PURE__ */ jsx16(Fragment4, { children: "Thought process" });
1144
1441
  }
1145
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1442
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1146
1443
  "Thought for ",
1147
1444
  duration,
1148
1445
  " seconds"
@@ -1151,7 +1448,7 @@ var getThinkingMessage = (isStreaming, duration) => {
1151
1448
  var ReasoningTrigger = memo2(
1152
1449
  ({ className, children, ...props }) => {
1153
1450
  const { isStreaming, isOpen, duration } = useReasoning();
1154
- return /* @__PURE__ */ jsx14(
1451
+ return /* @__PURE__ */ jsx16(
1155
1452
  CollapsibleTrigger2,
1156
1453
  {
1157
1454
  className: cn(
@@ -1159,10 +1456,10 @@ var ReasoningTrigger = memo2(
1159
1456
  className
1160
1457
  ),
1161
1458
  ...props,
1162
- children: children ?? /* @__PURE__ */ jsxs8(Fragment4, { children: [
1163
- /* @__PURE__ */ jsx14(BrainIcon, { className: "size-4 flex-shrink-0" }),
1459
+ children: children ?? /* @__PURE__ */ jsxs11(Fragment4, { children: [
1460
+ /* @__PURE__ */ jsx16(BrainIcon, { className: "size-4 flex-shrink-0" }),
1164
1461
  getThinkingMessage(isStreaming, duration),
1165
- /* @__PURE__ */ jsx14(
1462
+ /* @__PURE__ */ jsx16(
1166
1463
  ChevronDownIcon3,
1167
1464
  {
1168
1465
  className: cn(
@@ -1177,7 +1474,7 @@ var ReasoningTrigger = memo2(
1177
1474
  }
1178
1475
  );
1179
1476
  var ReasoningContent = memo2(
1180
- ({ className, children, ...props }) => /* @__PURE__ */ jsx14(
1477
+ ({ className, children, ...props }) => /* @__PURE__ */ jsx16(
1181
1478
  CollapsibleContent2,
1182
1479
  {
1183
1480
  className: cn(
@@ -1186,7 +1483,7 @@ var ReasoningContent = memo2(
1186
1483
  className
1187
1484
  ),
1188
1485
  ...props,
1189
- children: /* @__PURE__ */ jsx14(Response, { className: "grid gap-2", children })
1486
+ children: /* @__PURE__ */ jsx16(Response, { className: "grid gap-2", children })
1190
1487
  }
1191
1488
  )
1192
1489
  );
@@ -1195,8 +1492,8 @@ ReasoningTrigger.displayName = "ReasoningTrigger";
1195
1492
  ReasoningContent.displayName = "ReasoningContent";
1196
1493
 
1197
1494
  // src/components/loader.tsx
1198
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1199
- var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1495
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1496
+ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs12(
1200
1497
  "svg",
1201
1498
  {
1202
1499
  height: size,
@@ -1205,10 +1502,10 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1205
1502
  viewBox: "0 0 16 16",
1206
1503
  width: size,
1207
1504
  children: [
1208
- /* @__PURE__ */ jsx15("title", { children: "Loader" }),
1209
- /* @__PURE__ */ jsxs9("g", { clipPath: "url(#clip0_2393_1490)", children: [
1210
- /* @__PURE__ */ jsx15("path", { d: "M8 0V4", stroke: "currentColor", strokeWidth: "1.5" }),
1211
- /* @__PURE__ */ jsx15(
1505
+ /* @__PURE__ */ jsx17("title", { children: "Loader" }),
1506
+ /* @__PURE__ */ jsxs12("g", { clipPath: "url(#clip0_2393_1490)", children: [
1507
+ /* @__PURE__ */ jsx17("path", { d: "M8 0V4", stroke: "currentColor", strokeWidth: "1.5" }),
1508
+ /* @__PURE__ */ jsx17(
1212
1509
  "path",
1213
1510
  {
1214
1511
  d: "M8 16V12",
@@ -1217,7 +1514,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1217
1514
  strokeWidth: "1.5"
1218
1515
  }
1219
1516
  ),
1220
- /* @__PURE__ */ jsx15(
1517
+ /* @__PURE__ */ jsx17(
1221
1518
  "path",
1222
1519
  {
1223
1520
  d: "M3.29773 1.52783L5.64887 4.7639",
@@ -1226,7 +1523,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1226
1523
  strokeWidth: "1.5"
1227
1524
  }
1228
1525
  ),
1229
- /* @__PURE__ */ jsx15(
1526
+ /* @__PURE__ */ jsx17(
1230
1527
  "path",
1231
1528
  {
1232
1529
  d: "M12.7023 1.52783L10.3511 4.7639",
@@ -1235,7 +1532,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1235
1532
  strokeWidth: "1.5"
1236
1533
  }
1237
1534
  ),
1238
- /* @__PURE__ */ jsx15(
1535
+ /* @__PURE__ */ jsx17(
1239
1536
  "path",
1240
1537
  {
1241
1538
  d: "M12.7023 14.472L10.3511 11.236",
@@ -1244,7 +1541,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1244
1541
  strokeWidth: "1.5"
1245
1542
  }
1246
1543
  ),
1247
- /* @__PURE__ */ jsx15(
1544
+ /* @__PURE__ */ jsx17(
1248
1545
  "path",
1249
1546
  {
1250
1547
  d: "M3.29773 14.472L5.64887 11.236",
@@ -1253,7 +1550,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1253
1550
  strokeWidth: "1.5"
1254
1551
  }
1255
1552
  ),
1256
- /* @__PURE__ */ jsx15(
1553
+ /* @__PURE__ */ jsx17(
1257
1554
  "path",
1258
1555
  {
1259
1556
  d: "M15.6085 5.52783L11.8043 6.7639",
@@ -1262,7 +1559,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1262
1559
  strokeWidth: "1.5"
1263
1560
  }
1264
1561
  ),
1265
- /* @__PURE__ */ jsx15(
1562
+ /* @__PURE__ */ jsx17(
1266
1563
  "path",
1267
1564
  {
1268
1565
  d: "M0.391602 10.472L4.19583 9.23598",
@@ -1271,7 +1568,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1271
1568
  strokeWidth: "1.5"
1272
1569
  }
1273
1570
  ),
1274
- /* @__PURE__ */ jsx15(
1571
+ /* @__PURE__ */ jsx17(
1275
1572
  "path",
1276
1573
  {
1277
1574
  d: "M15.6085 10.4722L11.8043 9.2361",
@@ -1280,7 +1577,7 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1280
1577
  strokeWidth: "1.5"
1281
1578
  }
1282
1579
  ),
1283
- /* @__PURE__ */ jsx15(
1580
+ /* @__PURE__ */ jsx17(
1284
1581
  "path",
1285
1582
  {
1286
1583
  d: "M0.391602 5.52783L4.19583 6.7639",
@@ -1290,11 +1587,11 @@ var LoaderIcon = ({ size = 16 }) => /* @__PURE__ */ jsxs9(
1290
1587
  }
1291
1588
  )
1292
1589
  ] }),
1293
- /* @__PURE__ */ jsx15("defs", { children: /* @__PURE__ */ jsx15("clipPath", { id: "clip0_2393_1490", children: /* @__PURE__ */ jsx15("rect", { fill: "white", height: "16", width: "16" }) }) })
1590
+ /* @__PURE__ */ jsx17("defs", { children: /* @__PURE__ */ jsx17("clipPath", { id: "clip0_2393_1490", children: /* @__PURE__ */ jsx17("rect", { fill: "white", height: "16", width: "16" }) }) })
1294
1591
  ]
1295
1592
  }
1296
1593
  );
1297
- var Loader = ({ className, size = 16, ...props }) => /* @__PURE__ */ jsx15(
1594
+ var Loader = ({ className, size = 16, ...props }) => /* @__PURE__ */ jsx17(
1298
1595
  "div",
1299
1596
  {
1300
1597
  className: cn(
@@ -1302,14 +1599,14 @@ var Loader = ({ className, size = 16, ...props }) => /* @__PURE__ */ jsx15(
1302
1599
  className
1303
1600
  ),
1304
1601
  ...props,
1305
- children: /* @__PURE__ */ jsx15(LoaderIcon, { size })
1602
+ children: /* @__PURE__ */ jsx17(LoaderIcon, { size })
1306
1603
  }
1307
1604
  );
1308
1605
 
1309
1606
  // src/ui/badge.tsx
1310
1607
  import { Slot as Slot2 } from "@radix-ui/react-slot";
1311
1608
  import { cva as cva3 } from "class-variance-authority";
1312
- import { jsx as jsx16 } from "react/jsx-runtime";
1609
+ import { jsx as jsx18 } from "react/jsx-runtime";
1313
1610
  var badgeVariants = cva3(
1314
1611
  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
1315
1612
  {
@@ -1333,7 +1630,7 @@ function Badge({
1333
1630
  ...props
1334
1631
  }) {
1335
1632
  const Comp = asChild ? Slot2 : "span";
1336
- return /* @__PURE__ */ jsx16(
1633
+ return /* @__PURE__ */ jsx18(
1337
1634
  Comp,
1338
1635
  {
1339
1636
  "data-slot": "badge",
@@ -1355,14 +1652,14 @@ import {
1355
1652
  import { isValidElement } from "react";
1356
1653
 
1357
1654
  // src/components/code-block.tsx
1358
- import { CheckIcon as CheckIcon3, CopyIcon } from "lucide-react";
1359
- import { createContext as createContext3, useContext as useContext3, useState as useState4 } from "react";
1655
+ import { CheckIcon as CheckIcon4, CopyIcon as CopyIcon2 } from "lucide-react";
1656
+ import { createContext as createContext3, useContext as useContext3, useState as useState5 } from "react";
1360
1657
  import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
1361
1658
  import {
1362
1659
  oneDark,
1363
1660
  oneLight
1364
1661
  } from "react-syntax-highlighter/dist/esm/styles/prism";
1365
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1662
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1366
1663
  var CodeBlockContext = createContext3({
1367
1664
  code: ""
1368
1665
  });
@@ -1373,7 +1670,7 @@ var CodeBlock = ({
1373
1670
  className,
1374
1671
  children,
1375
1672
  ...props
1376
- }) => /* @__PURE__ */ jsx17(CodeBlockContext.Provider, { value: { code }, children: /* @__PURE__ */ jsx17(
1673
+ }) => /* @__PURE__ */ jsx19(CodeBlockContext.Provider, { value: { code }, children: /* @__PURE__ */ jsx19(
1377
1674
  "div",
1378
1675
  {
1379
1676
  className: cn(
@@ -1381,8 +1678,8 @@ var CodeBlock = ({
1381
1678
  className
1382
1679
  ),
1383
1680
  ...props,
1384
- children: /* @__PURE__ */ jsxs10("div", { className: "relative max-h-96 overflow-y-auto", children: [
1385
- /* @__PURE__ */ jsx17(
1681
+ children: /* @__PURE__ */ jsxs13("div", { className: "relative max-h-96 overflow-y-auto", children: [
1682
+ /* @__PURE__ */ jsx19(
1386
1683
  SyntaxHighlighter,
1387
1684
  {
1388
1685
  className: "overflow-hidden dark:hidden",
@@ -1408,7 +1705,7 @@ var CodeBlock = ({
1408
1705
  children: code
1409
1706
  }
1410
1707
  ),
1411
- /* @__PURE__ */ jsx17(
1708
+ /* @__PURE__ */ jsx19(
1412
1709
  SyntaxHighlighter,
1413
1710
  {
1414
1711
  className: "hidden overflow-hidden dark:block",
@@ -1434,36 +1731,42 @@ var CodeBlock = ({
1434
1731
  children: code
1435
1732
  }
1436
1733
  ),
1437
- children && /* @__PURE__ */ jsx17("div", { className: "absolute top-2 right-2 flex items-center gap-2", children })
1734
+ children && /* @__PURE__ */ jsx19("div", { className: "absolute top-2 right-2 flex items-center gap-2", children })
1438
1735
  ] })
1439
1736
  }
1440
1737
  ) });
1441
1738
 
1442
1739
  // src/components/tool.tsx
1443
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1444
- var Tool = ({ className, ...props }) => /* @__PURE__ */ jsx18(
1740
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
1741
+ var Tool = ({ className, ...props }) => /* @__PURE__ */ jsx20(
1445
1742
  Collapsible,
1446
1743
  {
1447
1744
  className: cn("not-prose w-full rounded-md border border-[var(--chat-divider)]", className),
1448
1745
  ...props
1449
1746
  }
1450
1747
  );
1748
+ var STATUS_LABELS = {
1749
+ "input-streaming": "Pending",
1750
+ "input-available": "Running",
1751
+ "output-available": "Completed",
1752
+ "output-error": "Error",
1753
+ "approval-requested": "Awaiting approval",
1754
+ "approval-responded": "Approved",
1755
+ "output-denied": "Denied"
1756
+ };
1757
+ var STATUS_ICONS = {
1758
+ "input-streaming": /* @__PURE__ */ jsx20(CircleIcon2, { className: "size-4" }),
1759
+ "input-available": /* @__PURE__ */ jsx20(ClockIcon, { className: "size-4 animate-pulse" }),
1760
+ "output-available": /* @__PURE__ */ jsx20(CheckCircleIcon, { className: "size-4 text-green-600" }),
1761
+ "output-error": /* @__PURE__ */ jsx20(XCircleIcon, { className: "size-4 text-red-600" }),
1762
+ "approval-requested": /* @__PURE__ */ jsx20(ClockIcon, { className: "size-4 text-amber-600" }),
1763
+ "approval-responded": /* @__PURE__ */ jsx20(CheckCircleIcon, { className: "size-4 text-amber-600" }),
1764
+ "output-denied": /* @__PURE__ */ jsx20(XCircleIcon, { className: "size-4 text-muted-foreground" })
1765
+ };
1451
1766
  var getStatusBadge = (status) => {
1452
- const labels = {
1453
- "input-streaming": "Pending",
1454
- "input-available": "Running",
1455
- "output-available": "Completed",
1456
- "output-error": "Error"
1457
- };
1458
- const icons = {
1459
- "input-streaming": /* @__PURE__ */ jsx18(CircleIcon2, { className: "size-4" }),
1460
- "input-available": /* @__PURE__ */ jsx18(ClockIcon, { className: "size-4 animate-pulse" }),
1461
- "output-available": /* @__PURE__ */ jsx18(CheckCircleIcon, { className: "size-4 text-green-600" }),
1462
- "output-error": /* @__PURE__ */ jsx18(XCircleIcon, { className: "size-4 text-red-600" })
1463
- };
1464
- return /* @__PURE__ */ jsxs11(Badge, { className: "gap-1.5 rounded-full text-xs", variant: "secondary", children: [
1465
- icons[status],
1466
- labels[status]
1767
+ return /* @__PURE__ */ jsxs14(Badge, { className: "gap-1.5 rounded-full text-xs", variant: "secondary", children: [
1768
+ STATUS_ICONS[status],
1769
+ STATUS_LABELS[status]
1467
1770
  ] });
1468
1771
  };
1469
1772
  var ToolHeader = ({
@@ -1473,7 +1776,7 @@ var ToolHeader = ({
1473
1776
  toolName,
1474
1777
  state,
1475
1778
  ...props
1476
- }) => /* @__PURE__ */ jsxs11(
1779
+ }) => /* @__PURE__ */ jsxs14(
1477
1780
  CollapsibleTrigger2,
1478
1781
  {
1479
1782
  className: cn(
@@ -1482,16 +1785,16 @@ var ToolHeader = ({
1482
1785
  ),
1483
1786
  ...props,
1484
1787
  children: [
1485
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1486
- /* @__PURE__ */ jsx18(WrenchIcon, { className: "size-4 text-muted-foreground" }),
1487
- /* @__PURE__ */ jsx18("span", { className: "font-medium text-sm", children: title ?? toolName ?? type.split("-").slice(1).join("-") }),
1788
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
1789
+ /* @__PURE__ */ jsx20(WrenchIcon, { className: "size-4 text-muted-foreground" }),
1790
+ /* @__PURE__ */ jsx20("span", { className: "font-medium text-sm", children: title ?? toolName ?? type.split("-").slice(1).join("-") }),
1488
1791
  getStatusBadge(state)
1489
1792
  ] }),
1490
- /* @__PURE__ */ jsx18(ChevronDownIcon4, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
1793
+ /* @__PURE__ */ jsx20(ChevronDownIcon4, { className: "size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" })
1491
1794
  ]
1492
1795
  }
1493
1796
  );
1494
- var ToolContent = ({ className, ...props }) => /* @__PURE__ */ jsx18(
1797
+ var ToolContent = ({ className, ...props }) => /* @__PURE__ */ jsx20(
1495
1798
  CollapsibleContent2,
1496
1799
  {
1497
1800
  className: cn(
@@ -1501,9 +1804,9 @@ var ToolContent = ({ className, ...props }) => /* @__PURE__ */ jsx18(
1501
1804
  ...props
1502
1805
  }
1503
1806
  );
1504
- var ToolInput = ({ className, input, ...props }) => /* @__PURE__ */ jsxs11("div", { className: cn("space-y-2 overflow-hidden p-2", className), ...props, children: [
1505
- /* @__PURE__ */ jsx18("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Parameters" }),
1506
- /* @__PURE__ */ jsx18("div", { className: "rounded-md bg-muted/50", children: /* @__PURE__ */ jsx18(CodeBlock, { code: JSON.stringify(input, null, 2), language: "json" }) })
1807
+ var ToolInput = ({ className, input, ...props }) => /* @__PURE__ */ jsxs14("div", { className: cn("space-y-2 overflow-hidden p-2", className), ...props, children: [
1808
+ /* @__PURE__ */ jsx20("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Parameters" }),
1809
+ /* @__PURE__ */ jsx20("div", { className: "rounded-md bg-muted/50", children: /* @__PURE__ */ jsx20(CodeBlock, { code: JSON.stringify(input, null, 2), language: "json" }) })
1507
1810
  ] });
1508
1811
  var ToolOutput = ({
1509
1812
  className,
@@ -1514,15 +1817,15 @@ var ToolOutput = ({
1514
1817
  if (!(output || errorText)) {
1515
1818
  return null;
1516
1819
  }
1517
- let Output = /* @__PURE__ */ jsx18("div", { children: output });
1820
+ let Output = /* @__PURE__ */ jsx20("div", { children: output });
1518
1821
  if (typeof output === "object" && !isValidElement(output)) {
1519
- Output = /* @__PURE__ */ jsx18(CodeBlock, { code: JSON.stringify(output, null, 2), language: "json" });
1822
+ Output = /* @__PURE__ */ jsx20(CodeBlock, { code: JSON.stringify(output, null, 2), language: "json" });
1520
1823
  } else if (typeof output === "string") {
1521
- Output = /* @__PURE__ */ jsx18(CodeBlock, { code: output, language: "json" });
1824
+ Output = /* @__PURE__ */ jsx20(CodeBlock, { code: output, language: "json" });
1522
1825
  }
1523
- return /* @__PURE__ */ jsxs11("div", { className: cn("space-y-2 p-2", className), ...props, children: [
1524
- /* @__PURE__ */ jsx18("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: errorText ? "Error" : "Result" }),
1525
- /* @__PURE__ */ jsxs11(
1826
+ return /* @__PURE__ */ jsxs14("div", { className: cn("space-y-2 p-2", className), ...props, children: [
1827
+ /* @__PURE__ */ jsx20("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: errorText ? "Error" : "Result" }),
1828
+ /* @__PURE__ */ jsxs14(
1526
1829
  "div",
1527
1830
  {
1528
1831
  className: cn(
@@ -1530,7 +1833,7 @@ var ToolOutput = ({
1530
1833
  errorText ? "bg-destructive/10 text-destructive" : "bg-muted/50 text-foreground"
1531
1834
  ),
1532
1835
  children: [
1533
- errorText && /* @__PURE__ */ jsx18("div", { children: errorText }),
1836
+ errorText && /* @__PURE__ */ jsx20("div", { children: errorText }),
1534
1837
  Output
1535
1838
  ]
1536
1839
  }
@@ -1539,7 +1842,7 @@ var ToolOutput = ({
1539
1842
  };
1540
1843
 
1541
1844
  // src/components/suggestion2.tsx
1542
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
1845
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1543
1846
  function StarterMessages({
1544
1847
  className,
1545
1848
  prompts,
@@ -1547,7 +1850,7 @@ function StarterMessages({
1547
1850
  ...props
1548
1851
  }) {
1549
1852
  if (prompts.length === 0) return null;
1550
- return /* @__PURE__ */ jsx19(
1853
+ return /* @__PURE__ */ jsx21(
1551
1854
  "div",
1552
1855
  {
1553
1856
  className: cn(
@@ -1555,8 +1858,8 @@ function StarterMessages({
1555
1858
  className
1556
1859
  ),
1557
1860
  ...props,
1558
- children: prompts.map((prompt, index) => /* @__PURE__ */ jsxs12("div", { children: [
1559
- /* @__PURE__ */ jsx19(
1861
+ children: prompts.map((prompt, index) => /* @__PURE__ */ jsxs15("div", { children: [
1862
+ /* @__PURE__ */ jsx21(
1560
1863
  StarterMessageItem,
1561
1864
  {
1562
1865
  prompt,
@@ -1566,7 +1869,7 @@ function StarterMessages({
1566
1869
  index < prompts.length - 1 && // 1px-tall element used as a divider — same --chat-divider token
1567
1870
  // every other separator in the widget uses, so consumers only
1568
1871
  // need to override one variable to recolour all of them.
1569
- /* @__PURE__ */ jsx19("div", { className: "h-px mx-3", style: { backgroundColor: "var(--chat-divider)" } })
1872
+ /* @__PURE__ */ jsx21("div", { className: "h-px mx-3", style: { backgroundColor: "var(--chat-divider)" } })
1570
1873
  ] }, index))
1571
1874
  }
1572
1875
  );
@@ -1577,7 +1880,7 @@ function StarterMessageItem({
1577
1880
  onClick,
1578
1881
  ...props
1579
1882
  }) {
1580
- return /* @__PURE__ */ jsxs12(
1883
+ return /* @__PURE__ */ jsxs15(
1581
1884
  "button",
1582
1885
  {
1583
1886
  type: "button",
@@ -1592,8 +1895,8 @@ function StarterMessageItem({
1592
1895
  ),
1593
1896
  ...props,
1594
1897
  children: [
1595
- /* @__PURE__ */ jsx19("span", { className: "text-[13px] text-[hsl(var(--chat-text)/0.7)]", children: prompt.title }),
1596
- prompt.subtitle && /* @__PURE__ */ jsx19("span", { className: "block text-[11px] text-[hsl(var(--chat-text)/0.4)] mt-0.5", children: prompt.subtitle })
1898
+ /* @__PURE__ */ jsx21("span", { className: "text-[13px] text-[hsl(var(--chat-text)/0.7)]", children: prompt.title }),
1899
+ prompt.subtitle && /* @__PURE__ */ jsx21("span", { className: "block text-[11px] text-[hsl(var(--chat-text)/0.4)] mt-0.5", children: prompt.subtitle })
1597
1900
  ]
1598
1901
  }
1599
1902
  );
@@ -1601,7 +1904,7 @@ function StarterMessageItem({
1601
1904
 
1602
1905
  // src/contexts/chat-storage-context.tsx
1603
1906
  import { createContext as createContext4, useContext as useContext4 } from "react";
1604
- import { jsx as jsx20 } from "react/jsx-runtime";
1907
+ import { jsx as jsx22 } from "react/jsx-runtime";
1605
1908
  var ChatStorageContext = createContext4({
1606
1909
  storageKeyPrefix: ""
1607
1910
  });
@@ -1610,19 +1913,19 @@ function ChatStorageProvider({
1610
1913
  userId
1611
1914
  }) {
1612
1915
  const storageKeyPrefix = userId || "";
1613
- return /* @__PURE__ */ jsx20(ChatStorageContext.Provider, { value: { storageKeyPrefix }, children });
1916
+ return /* @__PURE__ */ jsx22(ChatStorageContext.Provider, { value: { storageKeyPrefix }, children });
1614
1917
  }
1615
1918
  function useChatStorageKey() {
1616
1919
  return useContext4(ChatStorageContext);
1617
1920
  }
1618
1921
 
1619
1922
  // src/components/interface.tsx
1620
- import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
1923
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
1621
1924
  function ChatInterface({ id, initialMessages, config, onClose, headerActions } = {}) {
1622
1925
  const { storageKeyPrefix } = useChatStorageKey();
1623
1926
  const storageKey = (key) => storageKeyPrefix ? `chat-${storageKeyPrefix}-${key}` : `chat-${key}`;
1624
1927
  const themeMode = config?.theme?.mode || "light";
1625
- const [input, setInput] = useState5("");
1928
+ const [input, setInput] = useState6("");
1626
1929
  const inputRef = useRef4(null);
1627
1930
  const inputPlugins = useInputPlugins({
1628
1931
  textareaRef: inputRef,
@@ -1630,26 +1933,26 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1630
1933
  setValue: setInput,
1631
1934
  plugins: config?.inputPlugins
1632
1935
  });
1633
- const [showHistory, setShowHistory] = useState5(false);
1634
- const [conversations, setConversations] = useState5([]);
1635
- const [loadingHistory, setLoadingHistory] = useState5(false);
1636
- const [historyLoaded, setHistoryLoaded] = useState5(false);
1637
- const [searchQuery, setSearchQuery] = useState5("");
1638
- const [uploadError, setUploadError] = useState5(null);
1936
+ const [showHistory, setShowHistory] = useState6(false);
1937
+ const [conversations, setConversations] = useState6([]);
1938
+ const [loadingHistory, setLoadingHistory] = useState6(false);
1939
+ const [historyLoaded, setHistoryLoaded] = useState6(false);
1940
+ const [searchQuery, setSearchQuery] = useState6("");
1941
+ const [uploadError, setUploadError] = useState6(null);
1639
1942
  useEffect5(() => {
1640
1943
  if (uploadError) {
1641
1944
  const timeoutId = setTimeout(() => setUploadError(null), 5e3);
1642
1945
  return () => clearTimeout(timeoutId);
1643
1946
  }
1644
1947
  }, [uploadError]);
1645
- const [tabs, setTabs] = useState5([]);
1646
- const [activeTabId, setActiveTabId] = useState5("");
1647
- const [initialTabCreated, setInitialTabCreated] = useState5(false);
1648
- const [isInitializing, setIsInitializing] = useState5(true);
1649
- const [isLoadingMessages, setIsLoadingMessages] = useState5(false);
1948
+ const [tabs, setTabs] = useState6([]);
1949
+ const [activeTabId, setActiveTabId] = useState6("");
1950
+ const [initialTabCreated, setInitialTabCreated] = useState6(false);
1951
+ const [isInitializing, setIsInitializing] = useState6(true);
1952
+ const [isLoadingMessages, setIsLoadingMessages] = useState6(false);
1650
1953
  const lastSyncedTabId = useRef4("");
1651
1954
  const hasInitialized = useRef4(false);
1652
- const { messages, sendMessage, status, setMessages } = useChat({
1955
+ const { messages, sendMessage, status, setMessages, stop, regenerate, error, clearError } = useChat({
1653
1956
  id: activeTabId || "temp-id",
1654
1957
  transport: new DefaultChatTransport({
1655
1958
  api: "/api/chat",
@@ -1696,8 +1999,8 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1696
1999
  mediaType: uploadResult.mediaType,
1697
2000
  size: uploadResult.size
1698
2001
  };
1699
- } catch (error) {
1700
- console.error(`Error uploading ${file.filename}:`, error);
2002
+ } catch (error2) {
2003
+ console.error(`Error uploading ${file.filename}:`, error2);
1701
2004
  return null;
1702
2005
  }
1703
2006
  });
@@ -1714,10 +2017,10 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1714
2017
  setUploadError(warnMsg);
1715
2018
  console.warn(warnMsg);
1716
2019
  }
1717
- } catch (error) {
2020
+ } catch (error2) {
1718
2021
  const errorMsg = "Error uploading files. Please try again.";
1719
2022
  setUploadError(errorMsg);
1720
- console.error("Error in file upload process:", error);
2023
+ console.error("Error in file upload process:", error2);
1721
2024
  return;
1722
2025
  }
1723
2026
  }
@@ -1738,12 +2041,12 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1738
2041
  };
1739
2042
  const AttachButton = () => {
1740
2043
  const attachments = usePromptInputAttachments();
1741
- return /* @__PURE__ */ jsx21(
2044
+ return /* @__PURE__ */ jsx23(
1742
2045
  PromptInputButton,
1743
2046
  {
1744
2047
  variant: "ghost",
1745
2048
  onClick: () => attachments.openFileDialog(),
1746
- children: /* @__PURE__ */ jsx21(PlusIcon2, { className: "size-4" })
2049
+ children: /* @__PURE__ */ jsx23(PlusIcon2, { className: "size-4" })
1747
2050
  }
1748
2051
  );
1749
2052
  };
@@ -1766,8 +2069,8 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1766
2069
  } else {
1767
2070
  console.error("Error loading messages:", response.status, response.statusText);
1768
2071
  }
1769
- } catch (error) {
1770
- console.error("Error loading conversation:", error);
2072
+ } catch (error2) {
2073
+ console.error("Error loading conversation:", error2);
1771
2074
  }
1772
2075
  };
1773
2076
  const generateUniqueTabId = () => {
@@ -1871,8 +2174,8 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
1871
2174
  const errorText = await response.text();
1872
2175
  console.error("[ChatInterface] Error response:", errorText);
1873
2176
  }
1874
- } catch (error) {
1875
- console.error("[ChatInterface] Error fetching chat history:", error);
2177
+ } catch (error2) {
2178
+ console.error("[ChatInterface] Error fetching chat history:", error2);
1876
2179
  } finally {
1877
2180
  setLoadingHistory(false);
1878
2181
  }
@@ -2005,22 +2308,27 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2005
2308
  const renderMessages = () => messages.map((message, index) => {
2006
2309
  const sourceParts = message.parts?.filter((part) => part.type === "source-url") || [];
2007
2310
  const fileParts = message.parts?.filter((part) => part.type === "file") || [];
2008
- return /* @__PURE__ */ jsxs13("div", { className: index > 0 ? "mt-6" : "", children: [
2009
- message.role === "assistant" && sourceParts.length > 0 && /* @__PURE__ */ jsxs13(Sources, { children: [
2010
- /* @__PURE__ */ jsx21(SourcesTrigger, { count: sourceParts.length }),
2011
- sourceParts.map((part, i) => /* @__PURE__ */ jsx21(SourcesContent, { children: /* @__PURE__ */ jsx21(
2311
+ const isLastMessage = index === messages.length - 1;
2312
+ const isStreamingThisMessage = isLastMessage && message.role === "assistant" && status !== "ready";
2313
+ const showActions = message.role === "assistant" && !isStreamingThisMessage;
2314
+ const showRegenerate = showActions && isLastMessage && status === "ready";
2315
+ const messageText = showActions ? (message.parts ?? []).filter((p) => p.type === "text").map((p) => p.text).join("\n\n") : "";
2316
+ return /* @__PURE__ */ jsxs16("div", { className: index > 0 ? "mt-6" : "", children: [
2317
+ message.role === "assistant" && sourceParts.length > 0 && /* @__PURE__ */ jsxs16(Sources, { children: [
2318
+ /* @__PURE__ */ jsx23(SourcesTrigger, { count: sourceParts.length }),
2319
+ /* @__PURE__ */ jsx23(SourcesContent, { children: sourceParts.map((part, i) => /* @__PURE__ */ jsx23(
2012
2320
  Source,
2013
2321
  {
2014
2322
  href: part.url,
2015
2323
  title: part.url
2016
2324
  },
2017
- `${message.id}-${i}`
2018
- ) }, `${message.id}-${i}`))
2325
+ `${message.id}-source-${i}`
2326
+ )) })
2019
2327
  ] }),
2020
- fileParts.length > 0 && /* @__PURE__ */ jsx21("div", { className: cn(
2328
+ fileParts.length > 0 && /* @__PURE__ */ jsx23("div", { className: cn(
2021
2329
  "flex mb-1",
2022
2330
  message.role === "user" ? "justify-end" : "justify-start"
2023
- ), children: /* @__PURE__ */ jsx21(
2331
+ ), children: /* @__PURE__ */ jsx23(
2024
2332
  MessageAttachments,
2025
2333
  {
2026
2334
  attachments: fileParts.map((part) => ({
@@ -2031,14 +2339,14 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2031
2339
  }))
2032
2340
  }
2033
2341
  ) }),
2034
- message.parts ? /* @__PURE__ */ jsx21("div", { className: "space-y-2", children: message.parts.map((part, i) => {
2342
+ message.parts ? /* @__PURE__ */ jsx23("div", { className: "space-y-2", children: message.parts.map((part, i) => {
2035
2343
  switch (part.type) {
2036
2344
  case "text":
2037
2345
  const isTextStreaming = status === "streaming" && i === message.parts.length - 1 && message.id === messages.at(-1)?.id;
2038
- return /* @__PURE__ */ jsx21(Fragment5, { children: /* @__PURE__ */ jsx21(Message, { from: message.role, children: /* @__PURE__ */ jsx21(MessageContent, { children: /* @__PURE__ */ jsx21(Response, { isStreaming: isTextStreaming, children: part.text }) }) }) }, `${message.id}-${i}`);
2346
+ return /* @__PURE__ */ jsx23(Fragment5, { children: /* @__PURE__ */ jsx23(Message, { from: message.role, children: /* @__PURE__ */ jsx23(MessageContent, { children: /* @__PURE__ */ jsx23(Response, { isStreaming: isTextStreaming, children: part.text }) }) }) }, `${message.id}-${i}`);
2039
2347
  case "reasoning":
2040
2348
  const isCurrentlyStreaming = status === "streaming" && i === message.parts.length - 1 && message.id === messages.at(-1)?.id;
2041
- return /* @__PURE__ */ jsxs13(
2349
+ return /* @__PURE__ */ jsxs16(
2042
2350
  Reasoning,
2043
2351
  {
2044
2352
  className: "w-full",
@@ -2046,8 +2354,8 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2046
2354
  defaultOpen: false,
2047
2355
  open: isCurrentlyStreaming ? true : void 0,
2048
2356
  children: [
2049
- /* @__PURE__ */ jsx21(ReasoningTrigger, {}),
2050
- /* @__PURE__ */ jsx21(ReasoningContent, { children: part.text })
2357
+ /* @__PURE__ */ jsx23(ReasoningTrigger, {}),
2358
+ /* @__PURE__ */ jsx23(ReasoningContent, { children: part.text })
2051
2359
  ]
2052
2360
  },
2053
2361
  `${message.id}-${i}`
@@ -2055,8 +2363,24 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2055
2363
  default:
2056
2364
  if (part.type.startsWith("tool-") || part.type === "dynamic-tool") {
2057
2365
  const toolPart = part;
2058
- return /* @__PURE__ */ jsxs13(Tool, { children: [
2059
- /* @__PURE__ */ jsx21(
2366
+ const resolvedToolName = toolPart.toolName ?? (part.type.startsWith("tool-") ? part.type.slice(5) : "");
2367
+ const customRenderer = config?.toolRenderers?.[resolvedToolName];
2368
+ if (customRenderer) {
2369
+ const rendered = customRenderer({
2370
+ type: part.type,
2371
+ toolName: resolvedToolName,
2372
+ toolCallId: toolPart.toolCallId,
2373
+ state: toolPart.state,
2374
+ input: toolPart.input,
2375
+ output: toolPart.output,
2376
+ errorText: toolPart.errorText
2377
+ });
2378
+ if (rendered !== null && rendered !== void 0) {
2379
+ return /* @__PURE__ */ jsx23(Fragment5, { children: rendered }, `${message.id}-${i}`);
2380
+ }
2381
+ }
2382
+ return /* @__PURE__ */ jsxs16(Tool, { children: [
2383
+ /* @__PURE__ */ jsx23(
2060
2384
  ToolHeader,
2061
2385
  {
2062
2386
  type: part.type,
@@ -2064,9 +2388,9 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2064
2388
  state: toolPart.state
2065
2389
  }
2066
2390
  ),
2067
- /* @__PURE__ */ jsxs13(ToolContent, { children: [
2068
- /* @__PURE__ */ jsx21(ToolInput, { input: toolPart.input }),
2069
- /* @__PURE__ */ jsx21(ToolOutput, { output: toolPart.output, errorText: toolPart.errorText })
2391
+ /* @__PURE__ */ jsxs16(ToolContent, { children: [
2392
+ /* @__PURE__ */ jsx23(ToolInput, { input: toolPart.input }),
2393
+ /* @__PURE__ */ jsx23(ToolOutput, { output: toolPart.output, errorText: toolPart.errorText })
2070
2394
  ] })
2071
2395
  ] }, `${message.id}-${i}`);
2072
2396
  }
@@ -2074,7 +2398,14 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2074
2398
  }
2075
2399
  }) }) : (
2076
2400
  /* Handle standard AI SDK messages with content or text property */
2077
- /* @__PURE__ */ jsx21(Fragment5, { children: /* @__PURE__ */ jsx21(Message, { from: message.role, children: /* @__PURE__ */ jsx21(MessageContent, { children: /* @__PURE__ */ jsx21(Response, { children: message.content || message.text }) }) }) }, `${message.id}-content`)
2401
+ /* @__PURE__ */ jsx23(Fragment5, { children: /* @__PURE__ */ jsx23(Message, { from: message.role, children: /* @__PURE__ */ jsx23(MessageContent, { children: /* @__PURE__ */ jsx23(Response, { children: message.content || message.text }) }) }) }, `${message.id}-content`)
2402
+ ),
2403
+ showActions && /* @__PURE__ */ jsx23(
2404
+ MessageActions,
2405
+ {
2406
+ text: messageText,
2407
+ onRegenerate: showRegenerate && regenerate ? () => regenerate() : void 0
2408
+ }
2078
2409
  )
2079
2410
  ] }, message.id);
2080
2411
  });
@@ -2102,8 +2433,8 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2102
2433
  setActiveTabId(selectedConversationId);
2103
2434
  await loadConversation(selectedConversationId);
2104
2435
  setShowHistory(false);
2105
- } catch (error) {
2106
- console.error("Error loading conversation:", error);
2436
+ } catch (error2) {
2437
+ console.error("Error loading conversation:", error2);
2107
2438
  }
2108
2439
  };
2109
2440
  const dropdownRef = useRef4(null);
@@ -2120,7 +2451,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2120
2451
  document.removeEventListener("mousedown", handleClickOutside);
2121
2452
  };
2122
2453
  }, [showHistory]);
2123
- return /* @__PURE__ */ jsx21("div", { className: cn("w-full h-full flex flex-col bg-white dark:bg-gray-900 overflow-hidden ring-1 ring-black/[0.02] dark:ring-white/[0.03]", themeMode === "dark" && "dark"), children: /* @__PURE__ */ jsxs13(
2454
+ return /* @__PURE__ */ jsx23("div", { className: cn("w-full h-full flex flex-col bg-white dark:bg-gray-900 overflow-hidden ring-1 ring-black/[0.02] dark:ring-white/[0.03]", themeMode === "dark" && "dark"), children: /* @__PURE__ */ jsxs16(
2124
2455
  "div",
2125
2456
  {
2126
2457
  className: cn(
@@ -2128,11 +2459,11 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2128
2459
  themeMode === "dark" && "dark"
2129
2460
  ),
2130
2461
  children: [
2131
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
2462
+ /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
2132
2463
  borderColor: "var(--chat-divider)",
2133
2464
  backgroundColor: "var(--chat-header-bg)"
2134
2465
  }, children: [
2135
- /* @__PURE__ */ jsx21("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-hide py-0.5 scroll-smooth", children: tabs.map((tab, index) => /* @__PURE__ */ jsxs13(
2466
+ /* @__PURE__ */ jsx23("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-hide py-0.5 scroll-smooth", children: tabs.map((tab, index) => /* @__PURE__ */ jsxs16(
2136
2467
  "div",
2137
2468
  {
2138
2469
  className: "relative flex items-center gap-1.5 px-3 py-1.5 rounded-lg cursor-pointer transition-all duration-150 group flex-shrink-0 min-w-0",
@@ -2155,8 +2486,8 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2155
2486
  switchToTab(tab.id);
2156
2487
  },
2157
2488
  children: [
2158
- /* @__PURE__ */ jsx21("span", { className: "truncate max-w-28 text-[13px] font-medium transition-colors", children: tab.title }),
2159
- tabs.length > 1 && /* @__PURE__ */ jsx21(
2489
+ /* @__PURE__ */ jsx23("span", { className: "truncate max-w-28 text-[13px] font-medium transition-colors", children: tab.title }),
2490
+ tabs.length > 1 && /* @__PURE__ */ jsx23(
2160
2491
  "button",
2161
2492
  {
2162
2493
  onClick: (e) => {
@@ -2175,15 +2506,15 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2175
2506
  e.currentTarget.style.opacity = tab.isActive ? "0.6" : "0";
2176
2507
  e.currentTarget.style.backgroundColor = "transparent";
2177
2508
  },
2178
- children: /* @__PURE__ */ jsx21(XIcon2, { className: "h-3 w-3", strokeWidth: 2.5 })
2509
+ children: /* @__PURE__ */ jsx23(XIcon3, { className: "h-3 w-3", strokeWidth: 2.5 })
2179
2510
  }
2180
2511
  )
2181
2512
  ]
2182
2513
  },
2183
2514
  tab.id
2184
2515
  )) }),
2185
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [
2186
- /* @__PURE__ */ jsx21(
2516
+ /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-0.5 flex-shrink-0", children: [
2517
+ /* @__PURE__ */ jsx23(
2187
2518
  "button",
2188
2519
  {
2189
2520
  onClick: createNewTab,
@@ -2200,11 +2531,11 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2200
2531
  e.currentTarget.style.backgroundColor = "transparent";
2201
2532
  },
2202
2533
  title: "New Chat",
2203
- children: /* @__PURE__ */ jsx21(PlusIcon2, { className: "h-4 w-4", strokeWidth: 2 })
2534
+ children: /* @__PURE__ */ jsx23(PlusIcon2, { className: "h-4 w-4", strokeWidth: 2 })
2204
2535
  }
2205
2536
  ),
2206
- /* @__PURE__ */ jsxs13("div", { className: "relative", ref: dropdownRef, children: [
2207
- /* @__PURE__ */ jsx21(
2537
+ /* @__PURE__ */ jsxs16("div", { className: "relative", ref: dropdownRef, children: [
2538
+ /* @__PURE__ */ jsx23(
2208
2539
  "button",
2209
2540
  {
2210
2541
  onClick: () => setShowHistory(!showHistory),
@@ -2226,17 +2557,17 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2226
2557
  }
2227
2558
  },
2228
2559
  title: "Chat History",
2229
- children: /* @__PURE__ */ jsx21(HistoryIcon, { className: "h-4 w-4", strokeWidth: 2 })
2560
+ children: /* @__PURE__ */ jsx23(HistoryIcon, { className: "h-4 w-4", strokeWidth: 2 })
2230
2561
  }
2231
2562
  ),
2232
- showHistory && /* @__PURE__ */ jsxs13("div", { className: "absolute right-0 top-full mt-1.5 w-72 rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] z-50 animate-in fade-in slide-in-from-top-1 duration-150 overflow-hidden", style: {
2563
+ showHistory && /* @__PURE__ */ jsxs16("div", { className: "absolute right-0 top-full mt-1.5 w-72 rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] z-50 animate-in fade-in slide-in-from-top-1 duration-150 overflow-hidden", style: {
2233
2564
  backgroundColor: "hsl(var(--chat-background))",
2234
2565
  border: `1px solid ${"var(--chat-divider)"}`
2235
2566
  }, children: [
2236
- /* @__PURE__ */ jsx21("div", { className: "p-2.5 border-b", style: {
2567
+ /* @__PURE__ */ jsx23("div", { className: "p-2.5 border-b", style: {
2237
2568
  borderColor: "var(--chat-divider)",
2238
2569
  backgroundColor: "var(--chat-overlay)"
2239
- }, children: /* @__PURE__ */ jsx21("div", { className: "relative", children: /* @__PURE__ */ jsx21(
2570
+ }, children: /* @__PURE__ */ jsx23("div", { className: "relative", children: /* @__PURE__ */ jsx23(
2240
2571
  "input",
2241
2572
  {
2242
2573
  type: "text",
@@ -2251,25 +2582,25 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2251
2582
  }
2252
2583
  }
2253
2584
  ) }) }),
2254
- /* @__PURE__ */ jsx21("div", { className: "max-h-[300px] overflow-y-auto ai-assistant-scrollbar", children: loadingHistory ? /* @__PURE__ */ jsx21("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx21("div", { className: "text-[13px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Loading..." }) }) : conversations.length === 0 ? /* @__PURE__ */ jsxs13("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
2255
- /* @__PURE__ */ jsx21("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
2585
+ /* @__PURE__ */ jsx23("div", { className: "max-h-[300px] overflow-y-auto ai-assistant-scrollbar", children: loadingHistory ? /* @__PURE__ */ jsx23("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx23("div", { className: "text-[13px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Loading..." }) }) : conversations.length === 0 ? /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
2586
+ /* @__PURE__ */ jsx23("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
2256
2587
  backgroundColor: "hsl(var(--chat-surface))"
2257
- }, children: /* @__PURE__ */ jsx21(MessageSquareIcon, { className: "h-5 w-5", style: { color: "hsl(var(--chat-text-subtle))" }, strokeWidth: 2 }) }),
2258
- /* @__PURE__ */ jsx21("p", { className: "text-[13px] font-medium mb-0.5", style: { color: "hsl(var(--chat-text))" }, children: "No Conversations" }),
2259
- /* @__PURE__ */ jsx21("p", { className: "text-[12px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Start a new chat to begin" })
2260
- ] }) : groupedConversations.length === 0 ? /* @__PURE__ */ jsxs13("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
2261
- /* @__PURE__ */ jsx21("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
2588
+ }, children: /* @__PURE__ */ jsx23(MessageSquareIcon, { className: "h-5 w-5", style: { color: "hsl(var(--chat-text-subtle))" }, strokeWidth: 2 }) }),
2589
+ /* @__PURE__ */ jsx23("p", { className: "text-[13px] font-medium mb-0.5", style: { color: "hsl(var(--chat-text))" }, children: "No Conversations" }),
2590
+ /* @__PURE__ */ jsx23("p", { className: "text-[12px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Start a new chat to begin" })
2591
+ ] }) : groupedConversations.length === 0 ? /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
2592
+ /* @__PURE__ */ jsx23("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
2262
2593
  backgroundColor: "hsl(var(--chat-surface))"
2263
- }, children: /* @__PURE__ */ jsx21(SearchIcon, { className: "h-5 w-5", style: { color: "hsl(var(--chat-text-subtle))" }, strokeWidth: 2 }) }),
2264
- /* @__PURE__ */ jsx21("p", { className: "text-[13px] font-medium mb-0.5", style: { color: "hsl(var(--chat-text))" }, children: "No Results" }),
2265
- /* @__PURE__ */ jsx21("p", { className: "text-[12px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Try a different search" })
2266
- ] }) : /* @__PURE__ */ jsx21("div", { className: "py-0.5", children: groupedConversations.map(([groupName, groupConversations]) => /* @__PURE__ */ jsxs13("div", { className: "mb-0.5", children: [
2267
- /* @__PURE__ */ jsx21("div", { className: "px-2.5 py-1 sticky top-0 backdrop-blur-sm z-10", style: {
2594
+ }, children: /* @__PURE__ */ jsx23(SearchIcon, { className: "h-5 w-5", style: { color: "hsl(var(--chat-text-subtle))" }, strokeWidth: 2 }) }),
2595
+ /* @__PURE__ */ jsx23("p", { className: "text-[13px] font-medium mb-0.5", style: { color: "hsl(var(--chat-text))" }, children: "No Results" }),
2596
+ /* @__PURE__ */ jsx23("p", { className: "text-[12px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Try a different search" })
2597
+ ] }) : /* @__PURE__ */ jsx23("div", { className: "py-0.5", children: groupedConversations.map(([groupName, groupConversations]) => /* @__PURE__ */ jsxs16("div", { className: "mb-0.5", children: [
2598
+ /* @__PURE__ */ jsx23("div", { className: "px-2.5 py-1 sticky top-0 backdrop-blur-sm z-10", style: {
2268
2599
  backgroundColor: "var(--chat-header-bg-strong)"
2269
- }, children: /* @__PURE__ */ jsx21("h3", { className: "text-[10px] font-semibold uppercase tracking-wide", style: { color: "hsl(var(--chat-text-muted))" }, children: groupName }) }),
2270
- /* @__PURE__ */ jsx21("div", { className: "px-1 space-y-0.5", children: groupConversations.map((conversation) => {
2600
+ }, children: /* @__PURE__ */ jsx23("h3", { className: "text-[10px] font-semibold uppercase tracking-wide", style: { color: "hsl(var(--chat-text-muted))" }, children: groupName }) }),
2601
+ /* @__PURE__ */ jsx23("div", { className: "px-1 space-y-0.5", children: groupConversations.map((conversation) => {
2271
2602
  const isActiveConversation = activeTabId === conversation.id;
2272
- return /* @__PURE__ */ jsx21(
2603
+ return /* @__PURE__ */ jsx23(
2273
2604
  "button",
2274
2605
  {
2275
2606
  className: "w-full px-2 py-1 rounded-md transition-all duration-150 text-left group relative",
@@ -2287,12 +2618,12 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2287
2618
  }
2288
2619
  },
2289
2620
  onClick: () => handleSelectConversation(conversation.id, conversation.title),
2290
- children: /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1.5", children: [
2291
- /* @__PURE__ */ jsx21("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx21("p", { className: "text-[12px] line-clamp-1 transition-colors leading-tight", style: {
2621
+ children: /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1.5", children: [
2622
+ /* @__PURE__ */ jsx23("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx23("p", { className: "text-[12px] line-clamp-1 transition-colors leading-tight", style: {
2292
2623
  fontWeight: isActiveConversation ? 500 : 400,
2293
2624
  color: isActiveConversation ? "hsl(var(--chat-text))" : "hsl(var(--chat-text-strong))"
2294
2625
  }, children: conversation.title }) }),
2295
- /* @__PURE__ */ jsx21(
2626
+ /* @__PURE__ */ jsx23(
2296
2627
  ChevronRightIcon2,
2297
2628
  {
2298
2629
  className: "h-3 w-3 transition-all duration-150 flex-shrink-0",
@@ -2312,7 +2643,7 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2312
2643
  ] })
2313
2644
  ] }),
2314
2645
  headerActions,
2315
- onClose && /* @__PURE__ */ jsx21(
2646
+ onClose && /* @__PURE__ */ jsx23(
2316
2647
  "button",
2317
2648
  {
2318
2649
  onClick: onClose,
@@ -2329,21 +2660,21 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2329
2660
  e.currentTarget.style.backgroundColor = "transparent";
2330
2661
  },
2331
2662
  title: "Close Chat",
2332
- children: /* @__PURE__ */ jsx21(XIcon2, { className: "h-4 w-4", strokeWidth: 2 })
2663
+ children: /* @__PURE__ */ jsx23(XIcon3, { className: "h-4 w-4", strokeWidth: 2 })
2333
2664
  }
2334
2665
  )
2335
2666
  ] })
2336
2667
  ] }),
2337
- /* @__PURE__ */ jsxs13(Conversation, { className: "flex-1 max-w-full ai-assistant-scrollbar", children: [
2338
- /* @__PURE__ */ jsxs13(ConversationContent, { className: "max-w-[96%] mx-auto py-6", children: [
2668
+ /* @__PURE__ */ jsxs16(Conversation, { className: "flex-1 max-w-full ai-assistant-scrollbar", children: [
2669
+ /* @__PURE__ */ jsxs16(ConversationContent, { className: "max-w-[96%] mx-auto py-6", children: [
2339
2670
  renderMessages(),
2340
- status === "submitted" && /* @__PURE__ */ jsx21("div", { className: "mt-6", children: /* @__PURE__ */ jsx21(Message, { from: "assistant", children: /* @__PURE__ */ jsx21(MessageContent, { children: /* @__PURE__ */ jsx21(Loader, { size: 16 }) }) }) })
2671
+ status === "submitted" && /* @__PURE__ */ jsx23("div", { className: "mt-6", children: /* @__PURE__ */ jsx23(Message, { from: "assistant", children: /* @__PURE__ */ jsx23(MessageContent, { children: /* @__PURE__ */ jsx23(Loader, { size: 16 }) }) }) })
2341
2672
  ] }),
2342
- /* @__PURE__ */ jsx21(ConversationScrollButton, {})
2673
+ /* @__PURE__ */ jsx23(ConversationScrollButton, {})
2343
2674
  ] }),
2344
- /* @__PURE__ */ jsxs13("div", { className: "px-5 pb-5", children: [
2345
- uploadError && /* @__PURE__ */ jsx21("div", { className: "mb-3 px-4 py-3 bg-red-50 dark:bg-red-900/20 border border-red-200/60 dark:border-red-800/60 rounded-2xl text-sm text-red-700 dark:text-red-400 shadow-sm", children: uploadError }),
2346
- isInitializing || isLoadingMessages ? /* @__PURE__ */ jsx21("div", { className: "flex items-center justify-center py-8", role: "status", "aria-label": "Loading conversation", children: /* @__PURE__ */ jsx21("div", { className: "h-4 w-4 rounded-full border-2 border-current border-t-transparent animate-spin", style: { color: "hsl(var(--chat-text-muted))" } }) }) : messages.length === 0 && status !== "submitted" && config?.starterPrompts && /* @__PURE__ */ jsx21(
2675
+ /* @__PURE__ */ jsxs16("div", { className: "px-5 pb-5", children: [
2676
+ uploadError && /* @__PURE__ */ jsx23("div", { className: "mb-3 px-4 py-3 bg-red-50 dark:bg-red-900/20 border border-red-200/60 dark:border-red-800/60 rounded-2xl text-sm text-red-700 dark:text-red-400 shadow-sm", children: uploadError }),
2677
+ isInitializing || isLoadingMessages ? /* @__PURE__ */ jsx23("div", { className: "flex items-center justify-center py-8", role: "status", "aria-label": "Loading conversation", children: /* @__PURE__ */ jsx23("div", { className: "h-4 w-4 rounded-full border-2 border-current border-t-transparent animate-spin", style: { color: "hsl(var(--chat-text-muted))" } }) }) : messages.length === 0 && status !== "submitted" && config?.starterPrompts && /* @__PURE__ */ jsx23(
2347
2678
  StarterMessages,
2348
2679
  {
2349
2680
  prompts: config.starterPrompts,
@@ -2352,27 +2683,67 @@ function ChatInterface({ id, initialMessages, config, onClose, headerActions } =
2352
2683
  }
2353
2684
  }
2354
2685
  ),
2355
- /* @__PURE__ */ jsxs13(PromptInput, { onSubmit: handleSubmit, globalDrop: true, multiple: true, accept: "image/*", children: [
2356
- /* @__PURE__ */ jsxs13(PromptInputBody, { children: [
2357
- /* @__PURE__ */ jsx21(PromptInputAttachments, { children: (attachment) => /* @__PURE__ */ jsx21(PromptInputAttachment, { data: attachment }) }),
2358
- /* @__PURE__ */ jsxs13("div", { className: "relative", children: [
2359
- /* @__PURE__ */ jsx21(
2360
- PromptInputTextarea,
2361
- {
2362
- ref: inputRef,
2363
- onChange: (e) => setInput(e.target.value),
2364
- onKeyDown: inputPlugins.onKeyDown,
2365
- value: input
2366
- }
2367
- ),
2368
- inputPlugins.popover
2369
- ] })
2370
- ] }),
2371
- /* @__PURE__ */ jsxs13(PromptInputToolbar, { children: [
2372
- /* @__PURE__ */ jsx21(PromptInputTools, { children: config?.features?.fileUpload === true && /* @__PURE__ */ jsx21(AttachButton, {}) }),
2373
- /* @__PURE__ */ jsx21(PromptInputSubmit, { disabled: !input, status })
2374
- ] })
2375
- ] })
2686
+ /* @__PURE__ */ jsx23(
2687
+ ChatErrorBanner,
2688
+ {
2689
+ error: error ?? null,
2690
+ canRetry: messages.some((m) => m.role === "user"),
2691
+ onRetry: () => {
2692
+ clearError?.();
2693
+ regenerate?.();
2694
+ },
2695
+ onDismiss: clearError
2696
+ }
2697
+ ),
2698
+ inputPlugins.panel,
2699
+ /* @__PURE__ */ jsxs16(
2700
+ PromptInput,
2701
+ {
2702
+ onSubmit: handleSubmit,
2703
+ globalDrop: true,
2704
+ multiple: true,
2705
+ accept: config?.features?.fileUploadAccept ?? "image/*",
2706
+ maxFileSize: config?.features?.fileUploadMaxBytes,
2707
+ onError: (err) => {
2708
+ if (err.code === "max_file_size") {
2709
+ setUploadError(
2710
+ config?.features?.fileUploadMaxBytes ? `File too large (max ${Math.floor(
2711
+ config.features.fileUploadMaxBytes / 1024 / 1024
2712
+ )} MB).` : "File too large."
2713
+ );
2714
+ } else if (err.code === "accept") {
2715
+ setUploadError("That file type is not supported.");
2716
+ } else if (err.code === "max_files") {
2717
+ setUploadError("Too many files attached.");
2718
+ }
2719
+ },
2720
+ children: [
2721
+ /* @__PURE__ */ jsxs16(PromptInputBody, { children: [
2722
+ /* @__PURE__ */ jsx23(PromptInputAttachments, { children: (attachment) => /* @__PURE__ */ jsx23(PromptInputAttachment, { data: attachment }) }),
2723
+ /* @__PURE__ */ jsx23(
2724
+ PromptInputTextarea,
2725
+ {
2726
+ ref: inputRef,
2727
+ onChange: (e) => setInput(e.target.value),
2728
+ onKeyDown: inputPlugins.onKeyDown,
2729
+ value: input
2730
+ }
2731
+ )
2732
+ ] }),
2733
+ /* @__PURE__ */ jsxs16(PromptInputToolbar, { children: [
2734
+ /* @__PURE__ */ jsx23(PromptInputTools, { children: config?.features?.fileUpload === true && /* @__PURE__ */ jsx23(AttachButton, {}) }),
2735
+ /* @__PURE__ */ jsx23(
2736
+ PromptInputSubmit,
2737
+ {
2738
+ disabled: status === "streaming" || status === "submitted" ? false : !input,
2739
+ status,
2740
+ onStop: stop
2741
+ }
2742
+ )
2743
+ ] })
2744
+ ]
2745
+ }
2746
+ )
2376
2747
  ] })
2377
2748
  ]
2378
2749
  }
@@ -2418,7 +2789,7 @@ function toHslTripletIfHex(value) {
2418
2789
  }
2419
2790
 
2420
2791
  // src/ChatWidget.tsx
2421
- import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
2792
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
2422
2793
  function ChatWidget({
2423
2794
  userId,
2424
2795
  conversationId,
@@ -2435,14 +2806,15 @@ function ChatWidget({
2435
2806
  headerActions,
2436
2807
  open,
2437
2808
  onOpenChange,
2438
- inputPlugins
2809
+ inputPlugins,
2810
+ toolRenderers
2439
2811
  }) {
2440
2812
  const layout = display?.layout || "popup";
2441
2813
  const isControlled = open !== void 0;
2442
2814
  const showToggleButton = !isControlled && display?.showToggleButton !== false;
2443
2815
  const resizable = layout === "popup" && display?.resizable !== false;
2444
2816
  const size = display?.size || "default";
2445
- const [internalIsOpen, setInternalIsOpen] = useState6(
2817
+ const [internalIsOpen, setInternalIsOpen] = useState7(
2446
2818
  layout !== "popup" ? true : display?.defaultOpen || false
2447
2819
  );
2448
2820
  const isOpen = isControlled ? open : internalIsOpen;
@@ -2453,7 +2825,7 @@ function ChatWidget({
2453
2825
  setInternalIsOpen(next);
2454
2826
  }
2455
2827
  };
2456
- const [isResizing, setIsResizing] = useState6(false);
2828
+ const [isResizing, setIsResizing] = useState7(false);
2457
2829
  const containerRef = useRef5(null);
2458
2830
  const customStyles = useMemo4(() => {
2459
2831
  const styles = {};
@@ -2511,18 +2883,19 @@ function ChatWidget({
2511
2883
  theme,
2512
2884
  features,
2513
2885
  starterPrompts,
2514
- inputPlugins
2515
- }), [userId, model, systemPrompt, temperature, theme, features, starterPrompts, inputPlugins]);
2886
+ inputPlugins,
2887
+ toolRenderers
2888
+ }), [userId, model, systemPrompt, temperature, theme, features, starterPrompts, inputPlugins, toolRenderers]);
2516
2889
  const togglePosition = display?.toggleButtonPosition || { bottom: "24px", right: "24px" };
2517
2890
  const themeClass = theme?.mode === "dark" ? "dark" : "";
2518
2891
  if (layout === "inline") {
2519
- return /* @__PURE__ */ jsx22(ChatStorageProvider, { userId, children: /* @__PURE__ */ jsx22(
2892
+ return /* @__PURE__ */ jsx24(ChatStorageProvider, { userId, children: /* @__PURE__ */ jsx24(
2520
2893
  "div",
2521
2894
  {
2522
2895
  ref: containerRef,
2523
2896
  className: `chat-widget-container chat-widget-inline chat-widget-content ${themeClass} ${className || ""}`,
2524
2897
  style: customStyles,
2525
- children: /* @__PURE__ */ jsx22(
2898
+ children: /* @__PURE__ */ jsx24(
2526
2899
  ChatInterface,
2527
2900
  {
2528
2901
  id: conversationId,
@@ -2536,13 +2909,13 @@ function ChatWidget({
2536
2909
  ) });
2537
2910
  }
2538
2911
  if (layout === "page") {
2539
- return /* @__PURE__ */ jsx22(ChatStorageProvider, { userId, children: /* @__PURE__ */ jsx22(
2912
+ return /* @__PURE__ */ jsx24(ChatStorageProvider, { userId, children: /* @__PURE__ */ jsx24(
2540
2913
  "div",
2541
2914
  {
2542
2915
  ref: containerRef,
2543
2916
  className: `chat-widget-container chat-widget-page chat-widget-content ${themeClass} ${className || ""}`,
2544
2917
  style: customStyles,
2545
- children: /* @__PURE__ */ jsx22(
2918
+ children: /* @__PURE__ */ jsx24(
2546
2919
  ChatInterface,
2547
2920
  {
2548
2921
  id: conversationId,
@@ -2555,18 +2928,18 @@ function ChatWidget({
2555
2928
  }
2556
2929
  ) });
2557
2930
  }
2558
- return /* @__PURE__ */ jsxs14(ChatStorageProvider, { userId, children: [
2559
- showToggleButton && !isOpen && /* @__PURE__ */ jsx22(
2931
+ return /* @__PURE__ */ jsxs17(ChatStorageProvider, { userId, children: [
2932
+ showToggleButton && !isOpen && /* @__PURE__ */ jsx24(
2560
2933
  "button",
2561
2934
  {
2562
2935
  onClick: () => setIsOpen(true),
2563
2936
  className: "fixed z-50 rounded-full bg-primary text-primary-foreground shadow-lg hover:opacity-90 transition-all p-4",
2564
2937
  style: togglePosition,
2565
2938
  "aria-label": "Open chat",
2566
- children: /* @__PURE__ */ jsx22(MessageCircle, { className: "h-6 w-6" })
2939
+ children: /* @__PURE__ */ jsx24(MessageCircle, { className: "h-6 w-6" })
2567
2940
  }
2568
2941
  ),
2569
- isOpen && /* @__PURE__ */ jsxs14(
2942
+ isOpen && /* @__PURE__ */ jsxs17(
2570
2943
  "div",
2571
2944
  {
2572
2945
  ref: containerRef,
@@ -2575,7 +2948,7 @@ function ChatWidget({
2575
2948
  "data-resizing": isResizing,
2576
2949
  style: customStyles,
2577
2950
  children: [
2578
- resizable && /* @__PURE__ */ jsx22(
2951
+ resizable && /* @__PURE__ */ jsx24(
2579
2952
  "div",
2580
2953
  {
2581
2954
  onMouseDown: handleMouseDown,
@@ -2583,7 +2956,7 @@ function ChatWidget({
2583
2956
  "aria-label": "Resize chat widget"
2584
2957
  }
2585
2958
  ),
2586
- /* @__PURE__ */ jsx22("div", { className: "w-full h-full overflow-hidden", children: /* @__PURE__ */ jsx22(
2959
+ /* @__PURE__ */ jsx24("div", { className: "w-full h-full overflow-hidden", children: /* @__PURE__ */ jsx24(
2587
2960
  ChatInterface,
2588
2961
  {
2589
2962
  id: conversationId,
@@ -2604,7 +2977,7 @@ function ChatWidget({
2604
2977
  var ChatWidget_default = ChatWidget;
2605
2978
 
2606
2979
  // src/hooks/use-chat-theme.ts
2607
- import { useState as useState7, useEffect as useEffect7 } from "react";
2980
+ import { useState as useState8, useEffect as useEffect7 } from "react";
2608
2981
 
2609
2982
  // src/utils/models.ts
2610
2983
  var MODELS = [
@@ -2724,12 +3097,12 @@ var defaultThemeMode = "light";
2724
3097
  function useChatTheme() {
2725
3098
  const { storageKeyPrefix } = useChatStorageKey();
2726
3099
  const keyPrefix = storageKeyPrefix ? `chat-${storageKeyPrefix}-` : "chat-";
2727
- const [theme, setTheme] = useState7(defaultTheme);
2728
- const [conversationStarters, setConversationStarters] = useState7(defaultConversationStarters);
2729
- const [model, setModel] = useState7(defaultModel);
2730
- const [systemPrompt, setSystemPrompt] = useState7(defaultSystemPrompt);
2731
- const [temperature, setTemperature] = useState7(defaultTemperature);
2732
- const [themeMode, setThemeMode] = useState7(defaultThemeMode);
3100
+ const [theme, setTheme] = useState8(defaultTheme);
3101
+ const [conversationStarters, setConversationStarters] = useState8(defaultConversationStarters);
3102
+ const [model, setModel] = useState8(defaultModel);
3103
+ const [systemPrompt, setSystemPrompt] = useState8(defaultSystemPrompt);
3104
+ const [temperature, setTemperature] = useState8(defaultTemperature);
3105
+ const [themeMode, setThemeMode] = useState8(defaultThemeMode);
2733
3106
  useEffect7(() => {
2734
3107
  const savedTheme = localStorage.getItem(`${keyPrefix}theme`);
2735
3108
  if (savedTheme) {
@@ -2920,10 +3293,10 @@ function useChatTheme() {
2920
3293
 
2921
3294
  // src/ui/input.tsx
2922
3295
  import * as React3 from "react";
2923
- import { jsx as jsx23 } from "react/jsx-runtime";
3296
+ import { jsx as jsx25 } from "react/jsx-runtime";
2924
3297
  var Input = React3.forwardRef(
2925
3298
  ({ className, type, ...props }, ref) => {
2926
- return /* @__PURE__ */ jsx23(
3299
+ return /* @__PURE__ */ jsx25(
2927
3300
  "input",
2928
3301
  {
2929
3302
  type,
@@ -2941,23 +3314,23 @@ Input.displayName = "Input";
2941
3314
 
2942
3315
  // src/ui/dialog.tsx
2943
3316
  import * as DialogPrimitive from "@radix-ui/react-dialog";
2944
- import { XIcon as XIcon3 } from "lucide-react";
2945
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
3317
+ import { XIcon as XIcon4 } from "lucide-react";
3318
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
2946
3319
  function Dialog({
2947
3320
  ...props
2948
3321
  }) {
2949
- return /* @__PURE__ */ jsx24(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
3322
+ return /* @__PURE__ */ jsx26(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
2950
3323
  }
2951
3324
  function DialogPortal({
2952
3325
  ...props
2953
3326
  }) {
2954
- return /* @__PURE__ */ jsx24(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
3327
+ return /* @__PURE__ */ jsx26(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
2955
3328
  }
2956
3329
  function DialogOverlay({
2957
3330
  className,
2958
3331
  ...props
2959
3332
  }) {
2960
- return /* @__PURE__ */ jsx24(
3333
+ return /* @__PURE__ */ jsx26(
2961
3334
  DialogPrimitive.Overlay,
2962
3335
  {
2963
3336
  "data-slot": "dialog-overlay",
@@ -2975,9 +3348,9 @@ function DialogContent({
2975
3348
  showCloseButton = true,
2976
3349
  ...props
2977
3350
  }) {
2978
- return /* @__PURE__ */ jsxs15(DialogPortal, { "data-slot": "dialog-portal", children: [
2979
- /* @__PURE__ */ jsx24(DialogOverlay, {}),
2980
- /* @__PURE__ */ jsxs15(
3351
+ return /* @__PURE__ */ jsxs18(DialogPortal, { "data-slot": "dialog-portal", children: [
3352
+ /* @__PURE__ */ jsx26(DialogOverlay, {}),
3353
+ /* @__PURE__ */ jsxs18(
2981
3354
  DialogPrimitive.Content,
2982
3355
  {
2983
3356
  "data-slot": "dialog-content",
@@ -2988,14 +3361,14 @@ function DialogContent({
2988
3361
  ...props,
2989
3362
  children: [
2990
3363
  children,
2991
- showCloseButton && /* @__PURE__ */ jsxs15(
3364
+ showCloseButton && /* @__PURE__ */ jsxs18(
2992
3365
  DialogPrimitive.Close,
2993
3366
  {
2994
3367
  "data-slot": "dialog-close",
2995
3368
  className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
2996
3369
  children: [
2997
- /* @__PURE__ */ jsx24(XIcon3, {}),
2998
- /* @__PURE__ */ jsx24("span", { className: "sr-only", children: "Close" })
3370
+ /* @__PURE__ */ jsx26(XIcon4, {}),
3371
+ /* @__PURE__ */ jsx26("span", { className: "sr-only", children: "Close" })
2999
3372
  ]
3000
3373
  }
3001
3374
  )
@@ -3005,7 +3378,7 @@ function DialogContent({
3005
3378
  ] });
3006
3379
  }
3007
3380
  function DialogHeader({ className, ...props }) {
3008
- return /* @__PURE__ */ jsx24(
3381
+ return /* @__PURE__ */ jsx26(
3009
3382
  "div",
3010
3383
  {
3011
3384
  "data-slot": "dialog-header",
@@ -3018,7 +3391,7 @@ function DialogTitle({
3018
3391
  className,
3019
3392
  ...props
3020
3393
  }) {
3021
- return /* @__PURE__ */ jsx24(
3394
+ return /* @__PURE__ */ jsx26(
3022
3395
  DialogPrimitive.Title,
3023
3396
  {
3024
3397
  "data-slot": "dialog-title",
@@ -3031,7 +3404,7 @@ function DialogDescription({
3031
3404
  className,
3032
3405
  ...props
3033
3406
  }) {
3034
- return /* @__PURE__ */ jsx24(
3407
+ return /* @__PURE__ */ jsx26(
3035
3408
  DialogPrimitive.Description,
3036
3409
  {
3037
3410
  "data-slot": "dialog-description",
@@ -3052,6 +3425,11 @@ export {
3052
3425
  Input,
3053
3426
  StarterMessageItem,
3054
3427
  StarterMessages,
3428
+ Tool,
3429
+ ToolContent,
3430
+ ToolHeader,
3431
+ ToolInput,
3432
+ ToolOutput,
3055
3433
  ChatWidget_default as default,
3056
3434
  fontOptions,
3057
3435
  useChatStorageKey,