@wallavi/widget 1.3.6 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -81,6 +81,10 @@ interface ChatWidgetProps extends ChatWidgetConfig {
81
81
  className?: string;
82
82
  onClose?: () => void;
83
83
  onReset?: () => void;
84
+ /** Called when the user clicks the expand/minimize button in the header. Omit to hide the button. */
85
+ onExpand?: () => void;
86
+ /** Controls which icon the expand button shows (expand vs minimize). */
87
+ expanded?: boolean;
84
88
  }
85
89
  declare function getContrastColor(hex: string): string;
86
90
  declare function formatToolName(name: string): string;
@@ -88,41 +92,37 @@ declare function formatToolName(name: string): string;
88
92
  interface BubbleWidgetProps extends ChatWidgetConfig {
89
93
  /** Where the bubble is anchored. Default: "bottom-right" */
90
94
  position?: "bottom-right" | "bottom-left";
91
- /** Width of the chat panel in pixels. Default: 360 */
95
+ /** Chat panel width in px. Default: 360 */
92
96
  width?: number;
93
- /** Height of the chat panel in pixels. Default: 580 */
97
+ /** Chat panel height in px. Default: 580 */
94
98
  height?: number;
95
- /** Width of the expanded panel in pixels. Default: 640 */
99
+ /** Expanded panel width in px. Default: 640 */
96
100
  expandedWidth?: number;
97
- /** Height of the expanded panel. Default: "calc(100vh - 100px)" */
101
+ /** Expanded panel height. Default: "calc(100vh - 100px)" */
98
102
  expandedHeight?: string;
99
- /** Enable Cmd/Ctrl + shortcutKey to toggle the widget. Default: false */
103
+ /** Enable Cmd/Ctrl+shortcutKey to toggle. Default: false */
100
104
  keyboardShortcut?: boolean;
101
- /** Key to use with Cmd/Ctrl. Default: "k" */
105
+ /** Key used with Cmd/Ctrl. Default: "k" */
102
106
  shortcutKey?: string;
103
107
  /** Open the widget automatically on mount. Default: false */
104
108
  autoOpen?: boolean;
105
- /**
106
- * Image URL to show inside the bubble button.
107
- * Falls back to the Wallavi logo if not provided.
108
- */
109
+ /** URL of the image shown inside the bubble button */
109
110
  bubbleIconUrl?: string;
110
- /** Size of the bubble toggle button in pixels. Default: 52 */
111
+ /** Size of the bubble button in px. Default: 52 */
111
112
  bubbleSize?: number;
112
- /** className applied to the chat panel */
113
+ /** Extra className applied to the chat panel wrapper */
113
114
  panelClassName?: string;
114
115
  /**
115
- * Auto-fetch the agent's configuration from the Wallavi public API and apply it
116
- * as defaults (profilePicture, chatIcon, colors, messages, autoOpen, keyboardShortcut…).
117
- * The client only needs to pass `agentId` everything else self-configures.
118
- * Pass `autoConfig={false}` if you are providing all props manually.
116
+ * Auto-fetch agent config from the Wallavi dashboard and apply it as defaults.
117
+ * Colors, images, messages, autoOpen, keyboardShortcut, position — all self-configure.
118
+ * Clients only need to pass `agentId`. Set to `false` to supply all props manually.
119
119
  * @default true
120
120
  */
121
121
  autoConfig?: boolean;
122
122
  }
123
123
  declare function BubbleWidget({ position: positionProp, width, height, expandedWidth, expandedHeight, keyboardShortcut: keyboardShortcutProp, shortcutKey, autoOpen: autoOpenProp, bubbleIconUrl: bubbleIconUrlProp, bubbleSize, panelClassName, autoConfig, ...chatProps }: BubbleWidgetProps): react_jsx_runtime.JSX.Element;
124
124
 
125
- declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, hideCloseButton, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
125
+ declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, hideCloseButton, source, userContext, playgroundOverrides, className, onClose, onReset, onExpand, expanded, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
126
126
 
127
127
  interface UseChatOptions {
128
128
  agentId: string;
package/dist/index.d.ts CHANGED
@@ -81,6 +81,10 @@ interface ChatWidgetProps extends ChatWidgetConfig {
81
81
  className?: string;
82
82
  onClose?: () => void;
83
83
  onReset?: () => void;
84
+ /** Called when the user clicks the expand/minimize button in the header. Omit to hide the button. */
85
+ onExpand?: () => void;
86
+ /** Controls which icon the expand button shows (expand vs minimize). */
87
+ expanded?: boolean;
84
88
  }
85
89
  declare function getContrastColor(hex: string): string;
86
90
  declare function formatToolName(name: string): string;
@@ -88,41 +92,37 @@ declare function formatToolName(name: string): string;
88
92
  interface BubbleWidgetProps extends ChatWidgetConfig {
89
93
  /** Where the bubble is anchored. Default: "bottom-right" */
90
94
  position?: "bottom-right" | "bottom-left";
91
- /** Width of the chat panel in pixels. Default: 360 */
95
+ /** Chat panel width in px. Default: 360 */
92
96
  width?: number;
93
- /** Height of the chat panel in pixels. Default: 580 */
97
+ /** Chat panel height in px. Default: 580 */
94
98
  height?: number;
95
- /** Width of the expanded panel in pixels. Default: 640 */
99
+ /** Expanded panel width in px. Default: 640 */
96
100
  expandedWidth?: number;
97
- /** Height of the expanded panel. Default: "calc(100vh - 100px)" */
101
+ /** Expanded panel height. Default: "calc(100vh - 100px)" */
98
102
  expandedHeight?: string;
99
- /** Enable Cmd/Ctrl + shortcutKey to toggle the widget. Default: false */
103
+ /** Enable Cmd/Ctrl+shortcutKey to toggle. Default: false */
100
104
  keyboardShortcut?: boolean;
101
- /** Key to use with Cmd/Ctrl. Default: "k" */
105
+ /** Key used with Cmd/Ctrl. Default: "k" */
102
106
  shortcutKey?: string;
103
107
  /** Open the widget automatically on mount. Default: false */
104
108
  autoOpen?: boolean;
105
- /**
106
- * Image URL to show inside the bubble button.
107
- * Falls back to the Wallavi logo if not provided.
108
- */
109
+ /** URL of the image shown inside the bubble button */
109
110
  bubbleIconUrl?: string;
110
- /** Size of the bubble toggle button in pixels. Default: 52 */
111
+ /** Size of the bubble button in px. Default: 52 */
111
112
  bubbleSize?: number;
112
- /** className applied to the chat panel */
113
+ /** Extra className applied to the chat panel wrapper */
113
114
  panelClassName?: string;
114
115
  /**
115
- * Auto-fetch the agent's configuration from the Wallavi public API and apply it
116
- * as defaults (profilePicture, chatIcon, colors, messages, autoOpen, keyboardShortcut…).
117
- * The client only needs to pass `agentId` everything else self-configures.
118
- * Pass `autoConfig={false}` if you are providing all props manually.
116
+ * Auto-fetch agent config from the Wallavi dashboard and apply it as defaults.
117
+ * Colors, images, messages, autoOpen, keyboardShortcut, position — all self-configure.
118
+ * Clients only need to pass `agentId`. Set to `false` to supply all props manually.
119
119
  * @default true
120
120
  */
121
121
  autoConfig?: boolean;
122
122
  }
123
123
  declare function BubbleWidget({ position: positionProp, width, height, expandedWidth, expandedHeight, keyboardShortcut: keyboardShortcutProp, shortcutKey, autoOpen: autoOpenProp, bubbleIconUrl: bubbleIconUrlProp, bubbleSize, panelClassName, autoConfig, ...chatProps }: BubbleWidgetProps): react_jsx_runtime.JSX.Element;
124
124
 
125
- declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, hideCloseButton, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
125
+ declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, hideCloseButton, source, userContext, playgroundOverrides, className, onClose, onReset, onExpand, expanded, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
126
126
 
127
127
  interface UseChatOptions {
128
128
  agentId: string;
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
- var reactDom = require('react-dom');
4
+ var lucideReact = require('lucide-react');
5
5
  var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
- var lucideReact = require('lucide-react');
8
7
  var AvatarPrimitive = require('@radix-ui/react-avatar');
9
8
  var jsxRuntime = require('react/jsx-runtime');
10
9
  var ReactMarkdownLib = require('react-markdown');
@@ -293,6 +292,42 @@ function useChat({
293
292
  }, [streaming, messages, send]);
294
293
  return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
295
294
  }
295
+ function DefaultIcon() {
296
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
297
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "12", fill: "currentColor", opacity: 0.12 }),
298
+ /* @__PURE__ */ jsxRuntime.jsx(
299
+ "path",
300
+ {
301
+ d: "M4 8.5C4 6.57 5.57 5 7.5 5h9C18.43 5 20 6.57 20 8.5v5c0 1.93-1.57 3.5-3.5 3.5H13l-3 2.5V17H7.5C5.57 17 4 15.43 4 13.5v-5Z",
302
+ fill: "currentColor"
303
+ }
304
+ )
305
+ ] });
306
+ }
307
+ function ExpandIcon() {
308
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 11, height: 11 }, children: /* @__PURE__ */ jsxRuntime.jsx(
309
+ "path",
310
+ {
311
+ d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7",
312
+ stroke: "currentColor",
313
+ strokeWidth: 2,
314
+ strokeLinecap: "round",
315
+ strokeLinejoin: "round"
316
+ }
317
+ ) });
318
+ }
319
+ function MinimizeIcon() {
320
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 11, height: 11 }, children: /* @__PURE__ */ jsxRuntime.jsx(
321
+ "path",
322
+ {
323
+ d: "M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7",
324
+ stroke: "currentColor",
325
+ strokeWidth: 2,
326
+ strokeLinecap: "round",
327
+ strokeLinejoin: "round"
328
+ }
329
+ ) });
330
+ }
296
331
  var Avatar = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
297
332
  var AvatarImage = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
298
333
  var AvatarFallback = ({ style, ...p }) => /* @__PURE__ */ jsxRuntime.jsx(AvatarPrimitive__namespace.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
@@ -302,7 +337,9 @@ function ChatHeader({
302
337
  headerBg,
303
338
  headerText,
304
339
  onReset,
305
- onClose
340
+ onClose,
341
+ onExpand,
342
+ expanded
306
343
  }) {
307
344
  return /* @__PURE__ */ jsxRuntime.jsxs(
308
345
  "header",
@@ -324,6 +361,15 @@ function ChatHeader({
324
361
  ] })
325
362
  ] }),
326
363
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
364
+ onExpand && /* @__PURE__ */ jsxRuntime.jsx(
365
+ "button",
366
+ {
367
+ onClick: onExpand,
368
+ className: "rounded-full p-1.5 transition-colors hover:bg-white/10",
369
+ title: expanded ? "Minimize" : "Expand",
370
+ children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
371
+ }
372
+ ),
327
373
  /* @__PURE__ */ jsxRuntime.jsx(
328
374
  "button",
329
375
  {
@@ -629,7 +675,9 @@ function ChatWidget({
629
675
  playgroundOverrides,
630
676
  className,
631
677
  onClose,
632
- onReset
678
+ onReset,
679
+ onExpand,
680
+ expanded
633
681
  }) {
634
682
  const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
635
683
  const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
@@ -657,7 +705,9 @@ function ChatWidget({
657
705
  headerBg,
658
706
  headerText,
659
707
  onReset: handleReset,
660
- onClose: hideCloseButton ? void 0 : onClose
708
+ onClose: hideCloseButton ? void 0 : onClose,
709
+ onExpand,
710
+ expanded
661
711
  }
662
712
  ),
663
713
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -710,29 +760,63 @@ function ChatWidget({
710
760
  }
711
761
  );
712
762
  }
713
- var cn4 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
714
763
  var WALLAVI_PUBLIC_API = "https://wallavi-production.up.railway.app";
715
- function DefaultIcon() {
716
- return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
717
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "12", fill: "currentColor", opacity: 0.12 }),
718
- /* @__PURE__ */ jsxRuntime.jsx(
719
- "path",
720
- {
721
- d: "M4 8.5C4 6.57 5.57 5 7.5 5h9C18.43 5 20 6.57 20 8.5v5c0 1.93-1.57 3.5-3.5 3.5H13l-3 2.5V17H7.5C5.57 17 4 15.43 4 13.5v-5Z",
722
- fill: "currentColor"
723
- }
724
- )
725
- ] });
726
- }
727
- function CloseIcon() {
728
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 20, height: 20 }, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" }) });
729
- }
730
- function ExpandIcon() {
731
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 12, height: 12 }, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) });
732
- }
733
- function MinimizeIcon() {
734
- return /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 12, height: 12 }, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) });
764
+ var EMPTY = {
765
+ remoteConfig: {},
766
+ bubbleIconUrl: void 0,
767
+ autoOpen: false,
768
+ keyboardShortcut: false,
769
+ position: "bottom-right",
770
+ loading: false
771
+ };
772
+ function useAutoConfig(agentId, enabled) {
773
+ const [result, setResult] = react.useState(() => ({
774
+ ...EMPTY,
775
+ loading: enabled && Boolean(agentId)
776
+ }));
777
+ react.useEffect(() => {
778
+ if (!enabled || !agentId) {
779
+ setResult(EMPTY);
780
+ return;
781
+ }
782
+ let cancelled = false;
783
+ fetch(`${WALLAVI_PUBLIC_API}/api/public/widget/${agentId}`).then((r) => r.json()).then((body) => {
784
+ if (cancelled) return;
785
+ const cfg = body?.data ?? {};
786
+ const remote = {};
787
+ if (cfg.profilePicture != null) remote.profilePicture = cfg.profilePicture;
788
+ if (cfg.displayName != null) remote.displayName = cfg.displayName;
789
+ if (cfg.theme) remote.theme = cfg.theme;
790
+ if (cfg.userMessageColor) remote.userMessageColor = cfg.userMessageColor;
791
+ if (Array.isArray(cfg.initialMessages) && cfg.initialMessages.length > 0)
792
+ remote.initialMessages = cfg.initialMessages;
793
+ if (Array.isArray(cfg.suggestedMessages))
794
+ remote.suggestedMessages = cfg.suggestedMessages;
795
+ if (cfg.messagePlaceholder != null) remote.messagePlaceholder = cfg.messagePlaceholder;
796
+ if (cfg.watermark != null) remote.watermark = cfg.watermark;
797
+ if (cfg.footer != null) remote.footer = cfg.footer;
798
+ if (cfg.showThinking != null) remote.showThinking = cfg.showThinking;
799
+ if (cfg.regenerateMessage != null) remote.regenerateMessage = cfg.regenerateMessage;
800
+ setResult({
801
+ remoteConfig: remote,
802
+ bubbleIconUrl: cfg.chatIcon || cfg.profilePicture || void 0,
803
+ autoOpen: Boolean(cfg.autoOpen),
804
+ keyboardShortcut: Boolean(cfg.keyboardShortcut),
805
+ position: cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right",
806
+ loading: false
807
+ });
808
+ }).catch(() => {
809
+ if (!cancelled) setResult((r) => ({ ...r, loading: false }));
810
+ });
811
+ return () => {
812
+ cancelled = true;
813
+ };
814
+ }, [agentId, enabled]);
815
+ return result;
735
816
  }
817
+ var cn4 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
818
+ var KEY_EXPANDED = "wallavi_bubble_expanded";
819
+ var KEY_DISMISSED = "wallavi_bubble_dismissed";
736
820
  function BubbleWidget({
737
821
  position: positionProp,
738
822
  width = 360,
@@ -746,21 +830,27 @@ function BubbleWidget({
746
830
  bubbleSize = 52,
747
831
  panelClassName,
748
832
  autoConfig = true,
749
- // ChatWidgetConfig props
750
833
  ...chatProps
751
834
  }) {
752
835
  const [open, setOpen] = react.useState(false);
753
- const [expanded, setExpanded] = react.useState(
754
- () => typeof window !== "undefined" && localStorage.getItem("wallavi_bubble_expanded") === "true"
755
- );
756
- const [controlsPos, setControlsPos] = react.useState(null);
836
+ const [expanded, setExpanded] = react.useState(false);
757
837
  const panelRef = react.useRef(null);
758
838
  const autoOpenedRef = react.useRef(false);
759
- const [remoteConfig, setRemoteConfig] = react.useState({});
839
+ react.useEffect(() => {
840
+ if (localStorage.getItem(KEY_EXPANDED) === "true") setExpanded(true);
841
+ }, []);
842
+ const remote = useAutoConfig(chatProps.agentId, autoConfig);
760
843
  const [resolvedBubbleIcon, setResolvedBubbleIcon] = react.useState(bubbleIconUrlProp);
761
844
  const [resolvedAutoOpen, setResolvedAutoOpen] = react.useState(autoOpenProp);
762
845
  const [resolvedKeyboardShortcut, setResolvedKeyboardShortcut] = react.useState(keyboardShortcutProp);
763
846
  const [resolvedPosition, setResolvedPosition] = react.useState(positionProp ?? "bottom-right");
847
+ react.useEffect(() => {
848
+ if (remote.loading) return;
849
+ if (!bubbleIconUrlProp) setResolvedBubbleIcon(remote.bubbleIconUrl);
850
+ if (!autoOpenProp) setResolvedAutoOpen(remote.autoOpen);
851
+ if (!keyboardShortcutProp) setResolvedKeyboardShortcut(remote.keyboardShortcut);
852
+ if (!positionProp) setResolvedPosition(remote.position);
853
+ }, [remote.loading]);
764
854
  react.useEffect(() => {
765
855
  if (autoOpenProp) setResolvedAutoOpen(true);
766
856
  }, [autoOpenProp]);
@@ -773,38 +863,18 @@ function BubbleWidget({
773
863
  react.useEffect(() => {
774
864
  if (positionProp) setResolvedPosition(positionProp);
775
865
  }, [positionProp]);
776
- react.useEffect(() => {
777
- if (!autoConfig) return;
778
- fetch(`${WALLAVI_PUBLIC_API}/api/public/widget/${chatProps.agentId}`).then((r) => r.json()).then((body) => {
779
- const cfg = body?.data ?? {};
780
- const remote = {};
781
- if (cfg.profilePicture != null) remote.profilePicture = cfg.profilePicture;
782
- if (cfg.displayName != null) remote.displayName = cfg.displayName;
783
- if (cfg.theme) remote.theme = cfg.theme;
784
- if (cfg.userMessageColor) remote.userMessageColor = cfg.userMessageColor;
785
- if (Array.isArray(cfg.initialMessages) && cfg.initialMessages.length > 0) remote.initialMessages = cfg.initialMessages;
786
- if (Array.isArray(cfg.suggestedMessages)) remote.suggestedMessages = cfg.suggestedMessages;
787
- if (cfg.messagePlaceholder != null) remote.messagePlaceholder = cfg.messagePlaceholder;
788
- if (cfg.watermark != null) remote.watermark = cfg.watermark;
789
- if (cfg.footer != null) remote.footer = cfg.footer;
790
- if (cfg.showThinking != null) remote.showThinking = cfg.showThinking;
791
- if (cfg.regenerateMessage != null) remote.regenerateMessage = cfg.regenerateMessage;
792
- setRemoteConfig(remote);
793
- if (!bubbleIconUrlProp) {
794
- const icon = cfg.chatIcon || cfg.profilePicture;
795
- if (icon) setResolvedBubbleIcon(icon);
796
- }
797
- if (!autoOpenProp && cfg.autoOpen) setResolvedAutoOpen(true);
798
- if (!keyboardShortcutProp && cfg.keyboardShortcut) setResolvedKeyboardShortcut(true);
799
- if (!positionProp && cfg.alignChatBubbleButton) {
800
- setResolvedPosition(cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right");
801
- }
802
- }).catch(() => {
803
- });
804
- }, [autoConfig, chatProps.agentId]);
866
+ const definedChatProps = Object.fromEntries(
867
+ Object.entries(chatProps).filter(([, v]) => v !== void 0)
868
+ );
869
+ const mergedConfig = {
870
+ ...remote.remoteConfig,
871
+ ...definedChatProps,
872
+ agentId: chatProps.agentId,
873
+ agentName: chatProps.agentName
874
+ };
805
875
  react.useEffect(() => {
806
876
  if (!resolvedAutoOpen || autoOpenedRef.current) return;
807
- const dismissedUntil = Number(localStorage.getItem("wallavi_bubble_dismissed") ?? 0);
877
+ const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
808
878
  if (dismissedUntil < Date.now()) {
809
879
  autoOpenedRef.current = true;
810
880
  setOpen(true);
@@ -812,63 +882,35 @@ function BubbleWidget({
812
882
  }, [resolvedAutoOpen]);
813
883
  react.useEffect(() => {
814
884
  if (!resolvedKeyboardShortcut) return;
815
- const handler = (e) => {
885
+ const onKey = (e) => {
816
886
  if ((e.metaKey || e.ctrlKey) && e.key === shortcutKey) {
817
887
  e.preventDefault();
818
888
  setOpen((v) => !v);
819
889
  }
820
890
  };
821
- window.addEventListener("keydown", handler);
822
- return () => window.removeEventListener("keydown", handler);
891
+ window.addEventListener("keydown", onKey);
892
+ return () => window.removeEventListener("keydown", onKey);
823
893
  }, [resolvedKeyboardShortcut, shortcutKey]);
824
894
  react.useEffect(() => {
825
895
  if (!open) return;
826
- const id = setTimeout(() => {
827
- panelRef.current?.querySelector("textarea")?.focus();
828
- }, 50);
829
- return () => clearTimeout(id);
896
+ const t = setTimeout(
897
+ () => panelRef.current?.querySelector("textarea")?.focus(),
898
+ 50
899
+ );
900
+ return () => clearTimeout(t);
830
901
  }, [open]);
831
- const definedChatProps = Object.fromEntries(
832
- Object.entries(chatProps).filter(([, v]) => v !== void 0)
833
- );
834
- const mergedConfig = {
835
- ...remoteConfig,
836
- ...definedChatProps,
837
- // Required fields must always come from chatProps
838
- agentId: chatProps.agentId,
839
- agentName: chatProps.agentName
840
- };
841
902
  const handleClose = () => {
842
903
  setOpen(false);
843
- const expires = Date.now() + 24 * 60 * 60 * 1e3;
844
- localStorage.setItem("wallavi_bubble_dismissed", String(expires));
904
+ localStorage.setItem(KEY_DISMISSED, String(Date.now() + 24 * 60 * 60 * 1e3));
845
905
  };
846
906
  const toggleExpanded = () => setExpanded((v) => {
847
907
  const next = !v;
848
- localStorage.setItem("wallavi_bubble_expanded", String(next));
908
+ localStorage.setItem(KEY_EXPANDED, String(next));
849
909
  return next;
850
910
  });
851
911
  const isLeft = resolvedPosition === "bottom-left";
852
- react.useEffect(() => {
853
- if (!open) {
854
- setControlsPos(null);
855
- return;
856
- }
857
- const update = () => {
858
- const rect = panelRef.current?.getBoundingClientRect();
859
- if (!rect) return;
860
- setControlsPos({
861
- top: rect.top - 12,
862
- side: isLeft ? rect.left - 12 : window.innerWidth - rect.right - 12
863
- });
864
- };
865
- const t = setTimeout(update, 16);
866
- window.addEventListener("resize", update);
867
- return () => {
868
- clearTimeout(t);
869
- window.removeEventListener("resize", update);
870
- };
871
- }, [open, isLeft, expanded]);
912
+ const panelWidth = expanded ? Math.min(expandedWidth, typeof window !== "undefined" ? window.innerWidth - 40 : expandedWidth) : width;
913
+ const panelHeight = expanded ? expandedHeight : height;
872
914
  return /* @__PURE__ */ jsxRuntime.jsxs(
873
915
  "div",
874
916
  {
@@ -888,88 +930,24 @@ function BubbleWidget({
888
930
  {
889
931
  ref: panelRef,
890
932
  "aria-hidden": !open,
891
- style: { display: open ? "block" : "none", position: "relative" },
933
+ style: {
934
+ display: open ? "block" : "none",
935
+ width: panelWidth,
936
+ height: panelHeight,
937
+ transition: "width 0.3s ease, height 0.3s ease"
938
+ },
892
939
  children: /* @__PURE__ */ jsxRuntime.jsx(
893
- "div",
940
+ ChatWidget,
894
941
  {
895
- style: {
896
- width: expanded ? Math.min(expandedWidth, typeof window !== "undefined" ? window.innerWidth - 40 : expandedWidth) : width,
897
- height: expanded ? expandedHeight : height,
898
- transition: "width 0.3s ease, height 0.3s ease"
899
- },
900
- children: /* @__PURE__ */ jsxRuntime.jsx(
901
- ChatWidget,
902
- {
903
- ...mergedConfig,
904
- hideCloseButton: true,
905
- className: cn4("shadow-2xl h-full", panelClassName)
906
- }
907
- )
942
+ ...mergedConfig,
943
+ onClose: handleClose,
944
+ onExpand: toggleExpanded,
945
+ expanded,
946
+ className: cn4("shadow-2xl h-full", panelClassName)
908
947
  }
909
948
  )
910
949
  }
911
950
  ),
912
- open && controlsPos && typeof document !== "undefined" && reactDom.createPortal(
913
- /* @__PURE__ */ jsxRuntime.jsxs(
914
- "div",
915
- {
916
- style: {
917
- position: "fixed",
918
- top: controlsPos.top,
919
- [isLeft ? "left" : "right"]: controlsPos.side,
920
- zIndex: 10001,
921
- display: "flex",
922
- alignItems: "center",
923
- gap: 4
924
- },
925
- children: [
926
- /* @__PURE__ */ jsxRuntime.jsx(
927
- "button",
928
- {
929
- onClick: toggleExpanded,
930
- title: expanded ? "Minimize" : "Expand",
931
- style: {
932
- width: 24,
933
- height: 24,
934
- borderRadius: "50%",
935
- border: "1px solid rgba(0,0,0,0.1)",
936
- background: "var(--background, #fff)",
937
- boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
938
- display: "flex",
939
- alignItems: "center",
940
- justifyContent: "center",
941
- cursor: "pointer",
942
- color: "var(--muted-foreground, #888)"
943
- },
944
- children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
945
- }
946
- ),
947
- /* @__PURE__ */ jsxRuntime.jsx(
948
- "button",
949
- {
950
- onClick: handleClose,
951
- title: "Close",
952
- style: {
953
- width: 24,
954
- height: 24,
955
- borderRadius: "50%",
956
- border: "1px solid rgba(0,0,0,0.1)",
957
- background: "var(--background, #fff)",
958
- boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
959
- display: "flex",
960
- alignItems: "center",
961
- justifyContent: "center",
962
- cursor: "pointer",
963
- color: "var(--muted-foreground, #888)"
964
- },
965
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
966
- }
967
- )
968
- ]
969
- }
970
- ),
971
- document.body
972
- ),
973
951
  /* @__PURE__ */ jsxRuntime.jsx(
974
952
  "button",
975
953
  {
@@ -990,11 +968,11 @@ function BubbleWidget({
990
968
  background: open ? "var(--foreground, #19191c)" : "var(--background, #fff)",
991
969
  color: open ? "var(--background, #fff)" : "var(--foreground, #19191c)"
992
970
  },
993
- children: open ? /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) : resolvedBubbleIcon ? /* @__PURE__ */ jsxRuntime.jsx(
971
+ children: open ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { style: { width: 20, height: 20 } }) : resolvedBubbleIcon ? /* @__PURE__ */ jsxRuntime.jsx(
994
972
  "img",
995
973
  {
996
974
  src: resolvedBubbleIcon,
997
- alt: "Open chat",
975
+ alt: "",
998
976
  style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
999
977
  }
1000
978
  ) : /* @__PURE__ */ jsxRuntime.jsx(DefaultIcon, {})
package/dist/index.mjs CHANGED
@@ -1,8 +1,7 @@
1
1
  import { useState, useRef, useEffect, useCallback } from 'react';
2
- import { createPortal } from 'react-dom';
2
+ import { X, RotateCcw, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
- import { RotateCcw, X, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
6
5
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
7
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
8
7
  import ReactMarkdownLib from 'react-markdown';
@@ -267,6 +266,42 @@ function useChat({
267
266
  }, [streaming, messages, send]);
268
267
  return { messages, input, setInput, streaming, threadId, send, regenerate, reset };
269
268
  }
269
+ function DefaultIcon() {
270
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
271
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "12", fill: "currentColor", opacity: 0.12 }),
272
+ /* @__PURE__ */ jsx(
273
+ "path",
274
+ {
275
+ d: "M4 8.5C4 6.57 5.57 5 7.5 5h9C18.43 5 20 6.57 20 8.5v5c0 1.93-1.57 3.5-3.5 3.5H13l-3 2.5V17H7.5C5.57 17 4 15.43 4 13.5v-5Z",
276
+ fill: "currentColor"
277
+ }
278
+ )
279
+ ] });
280
+ }
281
+ function ExpandIcon() {
282
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 11, height: 11 }, children: /* @__PURE__ */ jsx(
283
+ "path",
284
+ {
285
+ d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7",
286
+ stroke: "currentColor",
287
+ strokeWidth: 2,
288
+ strokeLinecap: "round",
289
+ strokeLinejoin: "round"
290
+ }
291
+ ) });
292
+ }
293
+ function MinimizeIcon() {
294
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 11, height: 11 }, children: /* @__PURE__ */ jsx(
295
+ "path",
296
+ {
297
+ d: "M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7",
298
+ stroke: "currentColor",
299
+ strokeWidth: 2,
300
+ strokeLinecap: "round",
301
+ strokeLinejoin: "round"
302
+ }
303
+ ) });
304
+ }
270
305
  var Avatar = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Root, { style: { position: "relative", display: "flex", flexShrink: 0, overflow: "hidden", borderRadius: "9999px", ...style }, ...p });
271
306
  var AvatarImage = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Image, { style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", ...style }, ...p });
272
307
  var AvatarFallback = ({ style, ...p }) => /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, { style: { display: "flex", width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: "9999px", ...style }, ...p });
@@ -276,7 +311,9 @@ function ChatHeader({
276
311
  headerBg,
277
312
  headerText,
278
313
  onReset,
279
- onClose
314
+ onClose,
315
+ onExpand,
316
+ expanded
280
317
  }) {
281
318
  return /* @__PURE__ */ jsxs(
282
319
  "header",
@@ -298,6 +335,15 @@ function ChatHeader({
298
335
  ] })
299
336
  ] }),
300
337
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
338
+ onExpand && /* @__PURE__ */ jsx(
339
+ "button",
340
+ {
341
+ onClick: onExpand,
342
+ className: "rounded-full p-1.5 transition-colors hover:bg-white/10",
343
+ title: expanded ? "Minimize" : "Expand",
344
+ children: expanded ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(ExpandIcon, {})
345
+ }
346
+ ),
301
347
  /* @__PURE__ */ jsx(
302
348
  "button",
303
349
  {
@@ -603,7 +649,9 @@ function ChatWidget({
603
649
  playgroundOverrides,
604
650
  className,
605
651
  onClose,
606
- onReset
652
+ onReset,
653
+ onExpand,
654
+ expanded
607
655
  }) {
608
656
  const chat = useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides });
609
657
  const canRegenerate = regenerateMessage && chat.messages.length > 0 && chat.messages.at(-1)?.role === "assistant" && !chat.streaming;
@@ -631,7 +679,9 @@ function ChatWidget({
631
679
  headerBg,
632
680
  headerText,
633
681
  onReset: handleReset,
634
- onClose: hideCloseButton ? void 0 : onClose
682
+ onClose: hideCloseButton ? void 0 : onClose,
683
+ onExpand,
684
+ expanded
635
685
  }
636
686
  ),
637
687
  /* @__PURE__ */ jsx(
@@ -684,29 +734,63 @@ function ChatWidget({
684
734
  }
685
735
  );
686
736
  }
687
- var cn4 = (...inputs) => twMerge(clsx(inputs));
688
737
  var WALLAVI_PUBLIC_API = "https://wallavi-production.up.railway.app";
689
- function DefaultIcon() {
690
- return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
691
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "12", fill: "currentColor", opacity: 0.12 }),
692
- /* @__PURE__ */ jsx(
693
- "path",
694
- {
695
- d: "M4 8.5C4 6.57 5.57 5 7.5 5h9C18.43 5 20 6.57 20 8.5v5c0 1.93-1.57 3.5-3.5 3.5H13l-3 2.5V17H7.5C5.57 17 4 15.43 4 13.5v-5Z",
696
- fill: "currentColor"
697
- }
698
- )
699
- ] });
700
- }
701
- function CloseIcon() {
702
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 20, height: 20 }, children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round" }) });
703
- }
704
- function ExpandIcon() {
705
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 12, height: 12 }, children: /* @__PURE__ */ jsx("path", { d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) });
706
- }
707
- function MinimizeIcon() {
708
- return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 12, height: 12 }, children: /* @__PURE__ */ jsx("path", { d: "M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) });
738
+ var EMPTY = {
739
+ remoteConfig: {},
740
+ bubbleIconUrl: void 0,
741
+ autoOpen: false,
742
+ keyboardShortcut: false,
743
+ position: "bottom-right",
744
+ loading: false
745
+ };
746
+ function useAutoConfig(agentId, enabled) {
747
+ const [result, setResult] = useState(() => ({
748
+ ...EMPTY,
749
+ loading: enabled && Boolean(agentId)
750
+ }));
751
+ useEffect(() => {
752
+ if (!enabled || !agentId) {
753
+ setResult(EMPTY);
754
+ return;
755
+ }
756
+ let cancelled = false;
757
+ fetch(`${WALLAVI_PUBLIC_API}/api/public/widget/${agentId}`).then((r) => r.json()).then((body) => {
758
+ if (cancelled) return;
759
+ const cfg = body?.data ?? {};
760
+ const remote = {};
761
+ if (cfg.profilePicture != null) remote.profilePicture = cfg.profilePicture;
762
+ if (cfg.displayName != null) remote.displayName = cfg.displayName;
763
+ if (cfg.theme) remote.theme = cfg.theme;
764
+ if (cfg.userMessageColor) remote.userMessageColor = cfg.userMessageColor;
765
+ if (Array.isArray(cfg.initialMessages) && cfg.initialMessages.length > 0)
766
+ remote.initialMessages = cfg.initialMessages;
767
+ if (Array.isArray(cfg.suggestedMessages))
768
+ remote.suggestedMessages = cfg.suggestedMessages;
769
+ if (cfg.messagePlaceholder != null) remote.messagePlaceholder = cfg.messagePlaceholder;
770
+ if (cfg.watermark != null) remote.watermark = cfg.watermark;
771
+ if (cfg.footer != null) remote.footer = cfg.footer;
772
+ if (cfg.showThinking != null) remote.showThinking = cfg.showThinking;
773
+ if (cfg.regenerateMessage != null) remote.regenerateMessage = cfg.regenerateMessage;
774
+ setResult({
775
+ remoteConfig: remote,
776
+ bubbleIconUrl: cfg.chatIcon || cfg.profilePicture || void 0,
777
+ autoOpen: Boolean(cfg.autoOpen),
778
+ keyboardShortcut: Boolean(cfg.keyboardShortcut),
779
+ position: cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right",
780
+ loading: false
781
+ });
782
+ }).catch(() => {
783
+ if (!cancelled) setResult((r) => ({ ...r, loading: false }));
784
+ });
785
+ return () => {
786
+ cancelled = true;
787
+ };
788
+ }, [agentId, enabled]);
789
+ return result;
709
790
  }
791
+ var cn4 = (...inputs) => twMerge(clsx(inputs));
792
+ var KEY_EXPANDED = "wallavi_bubble_expanded";
793
+ var KEY_DISMISSED = "wallavi_bubble_dismissed";
710
794
  function BubbleWidget({
711
795
  position: positionProp,
712
796
  width = 360,
@@ -720,21 +804,27 @@ function BubbleWidget({
720
804
  bubbleSize = 52,
721
805
  panelClassName,
722
806
  autoConfig = true,
723
- // ChatWidgetConfig props
724
807
  ...chatProps
725
808
  }) {
726
809
  const [open, setOpen] = useState(false);
727
- const [expanded, setExpanded] = useState(
728
- () => typeof window !== "undefined" && localStorage.getItem("wallavi_bubble_expanded") === "true"
729
- );
730
- const [controlsPos, setControlsPos] = useState(null);
810
+ const [expanded, setExpanded] = useState(false);
731
811
  const panelRef = useRef(null);
732
812
  const autoOpenedRef = useRef(false);
733
- const [remoteConfig, setRemoteConfig] = useState({});
813
+ useEffect(() => {
814
+ if (localStorage.getItem(KEY_EXPANDED) === "true") setExpanded(true);
815
+ }, []);
816
+ const remote = useAutoConfig(chatProps.agentId, autoConfig);
734
817
  const [resolvedBubbleIcon, setResolvedBubbleIcon] = useState(bubbleIconUrlProp);
735
818
  const [resolvedAutoOpen, setResolvedAutoOpen] = useState(autoOpenProp);
736
819
  const [resolvedKeyboardShortcut, setResolvedKeyboardShortcut] = useState(keyboardShortcutProp);
737
820
  const [resolvedPosition, setResolvedPosition] = useState(positionProp ?? "bottom-right");
821
+ useEffect(() => {
822
+ if (remote.loading) return;
823
+ if (!bubbleIconUrlProp) setResolvedBubbleIcon(remote.bubbleIconUrl);
824
+ if (!autoOpenProp) setResolvedAutoOpen(remote.autoOpen);
825
+ if (!keyboardShortcutProp) setResolvedKeyboardShortcut(remote.keyboardShortcut);
826
+ if (!positionProp) setResolvedPosition(remote.position);
827
+ }, [remote.loading]);
738
828
  useEffect(() => {
739
829
  if (autoOpenProp) setResolvedAutoOpen(true);
740
830
  }, [autoOpenProp]);
@@ -747,38 +837,18 @@ function BubbleWidget({
747
837
  useEffect(() => {
748
838
  if (positionProp) setResolvedPosition(positionProp);
749
839
  }, [positionProp]);
750
- useEffect(() => {
751
- if (!autoConfig) return;
752
- fetch(`${WALLAVI_PUBLIC_API}/api/public/widget/${chatProps.agentId}`).then((r) => r.json()).then((body) => {
753
- const cfg = body?.data ?? {};
754
- const remote = {};
755
- if (cfg.profilePicture != null) remote.profilePicture = cfg.profilePicture;
756
- if (cfg.displayName != null) remote.displayName = cfg.displayName;
757
- if (cfg.theme) remote.theme = cfg.theme;
758
- if (cfg.userMessageColor) remote.userMessageColor = cfg.userMessageColor;
759
- if (Array.isArray(cfg.initialMessages) && cfg.initialMessages.length > 0) remote.initialMessages = cfg.initialMessages;
760
- if (Array.isArray(cfg.suggestedMessages)) remote.suggestedMessages = cfg.suggestedMessages;
761
- if (cfg.messagePlaceholder != null) remote.messagePlaceholder = cfg.messagePlaceholder;
762
- if (cfg.watermark != null) remote.watermark = cfg.watermark;
763
- if (cfg.footer != null) remote.footer = cfg.footer;
764
- if (cfg.showThinking != null) remote.showThinking = cfg.showThinking;
765
- if (cfg.regenerateMessage != null) remote.regenerateMessage = cfg.regenerateMessage;
766
- setRemoteConfig(remote);
767
- if (!bubbleIconUrlProp) {
768
- const icon = cfg.chatIcon || cfg.profilePicture;
769
- if (icon) setResolvedBubbleIcon(icon);
770
- }
771
- if (!autoOpenProp && cfg.autoOpen) setResolvedAutoOpen(true);
772
- if (!keyboardShortcutProp && cfg.keyboardShortcut) setResolvedKeyboardShortcut(true);
773
- if (!positionProp && cfg.alignChatBubbleButton) {
774
- setResolvedPosition(cfg.alignChatBubbleButton === "left" ? "bottom-left" : "bottom-right");
775
- }
776
- }).catch(() => {
777
- });
778
- }, [autoConfig, chatProps.agentId]);
840
+ const definedChatProps = Object.fromEntries(
841
+ Object.entries(chatProps).filter(([, v]) => v !== void 0)
842
+ );
843
+ const mergedConfig = {
844
+ ...remote.remoteConfig,
845
+ ...definedChatProps,
846
+ agentId: chatProps.agentId,
847
+ agentName: chatProps.agentName
848
+ };
779
849
  useEffect(() => {
780
850
  if (!resolvedAutoOpen || autoOpenedRef.current) return;
781
- const dismissedUntil = Number(localStorage.getItem("wallavi_bubble_dismissed") ?? 0);
851
+ const dismissedUntil = Number(localStorage.getItem(KEY_DISMISSED) ?? 0);
782
852
  if (dismissedUntil < Date.now()) {
783
853
  autoOpenedRef.current = true;
784
854
  setOpen(true);
@@ -786,63 +856,35 @@ function BubbleWidget({
786
856
  }, [resolvedAutoOpen]);
787
857
  useEffect(() => {
788
858
  if (!resolvedKeyboardShortcut) return;
789
- const handler = (e) => {
859
+ const onKey = (e) => {
790
860
  if ((e.metaKey || e.ctrlKey) && e.key === shortcutKey) {
791
861
  e.preventDefault();
792
862
  setOpen((v) => !v);
793
863
  }
794
864
  };
795
- window.addEventListener("keydown", handler);
796
- return () => window.removeEventListener("keydown", handler);
865
+ window.addEventListener("keydown", onKey);
866
+ return () => window.removeEventListener("keydown", onKey);
797
867
  }, [resolvedKeyboardShortcut, shortcutKey]);
798
868
  useEffect(() => {
799
869
  if (!open) return;
800
- const id = setTimeout(() => {
801
- panelRef.current?.querySelector("textarea")?.focus();
802
- }, 50);
803
- return () => clearTimeout(id);
870
+ const t = setTimeout(
871
+ () => panelRef.current?.querySelector("textarea")?.focus(),
872
+ 50
873
+ );
874
+ return () => clearTimeout(t);
804
875
  }, [open]);
805
- const definedChatProps = Object.fromEntries(
806
- Object.entries(chatProps).filter(([, v]) => v !== void 0)
807
- );
808
- const mergedConfig = {
809
- ...remoteConfig,
810
- ...definedChatProps,
811
- // Required fields must always come from chatProps
812
- agentId: chatProps.agentId,
813
- agentName: chatProps.agentName
814
- };
815
876
  const handleClose = () => {
816
877
  setOpen(false);
817
- const expires = Date.now() + 24 * 60 * 60 * 1e3;
818
- localStorage.setItem("wallavi_bubble_dismissed", String(expires));
878
+ localStorage.setItem(KEY_DISMISSED, String(Date.now() + 24 * 60 * 60 * 1e3));
819
879
  };
820
880
  const toggleExpanded = () => setExpanded((v) => {
821
881
  const next = !v;
822
- localStorage.setItem("wallavi_bubble_expanded", String(next));
882
+ localStorage.setItem(KEY_EXPANDED, String(next));
823
883
  return next;
824
884
  });
825
885
  const isLeft = resolvedPosition === "bottom-left";
826
- useEffect(() => {
827
- if (!open) {
828
- setControlsPos(null);
829
- return;
830
- }
831
- const update = () => {
832
- const rect = panelRef.current?.getBoundingClientRect();
833
- if (!rect) return;
834
- setControlsPos({
835
- top: rect.top - 12,
836
- side: isLeft ? rect.left - 12 : window.innerWidth - rect.right - 12
837
- });
838
- };
839
- const t = setTimeout(update, 16);
840
- window.addEventListener("resize", update);
841
- return () => {
842
- clearTimeout(t);
843
- window.removeEventListener("resize", update);
844
- };
845
- }, [open, isLeft, expanded]);
886
+ const panelWidth = expanded ? Math.min(expandedWidth, typeof window !== "undefined" ? window.innerWidth - 40 : expandedWidth) : width;
887
+ const panelHeight = expanded ? expandedHeight : height;
846
888
  return /* @__PURE__ */ jsxs(
847
889
  "div",
848
890
  {
@@ -862,88 +904,24 @@ function BubbleWidget({
862
904
  {
863
905
  ref: panelRef,
864
906
  "aria-hidden": !open,
865
- style: { display: open ? "block" : "none", position: "relative" },
907
+ style: {
908
+ display: open ? "block" : "none",
909
+ width: panelWidth,
910
+ height: panelHeight,
911
+ transition: "width 0.3s ease, height 0.3s ease"
912
+ },
866
913
  children: /* @__PURE__ */ jsx(
867
- "div",
914
+ ChatWidget,
868
915
  {
869
- style: {
870
- width: expanded ? Math.min(expandedWidth, typeof window !== "undefined" ? window.innerWidth - 40 : expandedWidth) : width,
871
- height: expanded ? expandedHeight : height,
872
- transition: "width 0.3s ease, height 0.3s ease"
873
- },
874
- children: /* @__PURE__ */ jsx(
875
- ChatWidget,
876
- {
877
- ...mergedConfig,
878
- hideCloseButton: true,
879
- className: cn4("shadow-2xl h-full", panelClassName)
880
- }
881
- )
916
+ ...mergedConfig,
917
+ onClose: handleClose,
918
+ onExpand: toggleExpanded,
919
+ expanded,
920
+ className: cn4("shadow-2xl h-full", panelClassName)
882
921
  }
883
922
  )
884
923
  }
885
924
  ),
886
- open && controlsPos && typeof document !== "undefined" && createPortal(
887
- /* @__PURE__ */ jsxs(
888
- "div",
889
- {
890
- style: {
891
- position: "fixed",
892
- top: controlsPos.top,
893
- [isLeft ? "left" : "right"]: controlsPos.side,
894
- zIndex: 10001,
895
- display: "flex",
896
- alignItems: "center",
897
- gap: 4
898
- },
899
- children: [
900
- /* @__PURE__ */ jsx(
901
- "button",
902
- {
903
- onClick: toggleExpanded,
904
- title: expanded ? "Minimize" : "Expand",
905
- style: {
906
- width: 24,
907
- height: 24,
908
- borderRadius: "50%",
909
- border: "1px solid rgba(0,0,0,0.1)",
910
- background: "var(--background, #fff)",
911
- boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
912
- display: "flex",
913
- alignItems: "center",
914
- justifyContent: "center",
915
- cursor: "pointer",
916
- color: "var(--muted-foreground, #888)"
917
- },
918
- children: expanded ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(ExpandIcon, {})
919
- }
920
- ),
921
- /* @__PURE__ */ jsx(
922
- "button",
923
- {
924
- onClick: handleClose,
925
- title: "Close",
926
- style: {
927
- width: 24,
928
- height: 24,
929
- borderRadius: "50%",
930
- border: "1px solid rgba(0,0,0,0.1)",
931
- background: "var(--background, #fff)",
932
- boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
933
- display: "flex",
934
- alignItems: "center",
935
- justifyContent: "center",
936
- cursor: "pointer",
937
- color: "var(--muted-foreground, #888)"
938
- },
939
- children: /* @__PURE__ */ jsx(CloseIcon, {})
940
- }
941
- )
942
- ]
943
- }
944
- ),
945
- document.body
946
- ),
947
925
  /* @__PURE__ */ jsx(
948
926
  "button",
949
927
  {
@@ -964,11 +942,11 @@ function BubbleWidget({
964
942
  background: open ? "var(--foreground, #19191c)" : "var(--background, #fff)",
965
943
  color: open ? "var(--background, #fff)" : "var(--foreground, #19191c)"
966
944
  },
967
- children: open ? /* @__PURE__ */ jsx(CloseIcon, {}) : resolvedBubbleIcon ? /* @__PURE__ */ jsx(
945
+ children: open ? /* @__PURE__ */ jsx(X, { style: { width: 20, height: 20 } }) : resolvedBubbleIcon ? /* @__PURE__ */ jsx(
968
946
  "img",
969
947
  {
970
948
  src: resolvedBubbleIcon,
971
- alt: "Open chat",
949
+ alt: "",
972
950
  style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
973
951
  }
974
952
  ) : /* @__PURE__ */ jsx(DefaultIcon, {})
package/package.json CHANGED
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "private": false,
35
35
  "types": "./dist/index.d.ts",
36
- "version": "1.3.6",
36
+ "version": "1.3.7",
37
37
  "scripts": {
38
38
  "build": "tsup",
39
39
  "typecheck": "tsc --noEmit"