@wallavi/widget 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -62,6 +62,12 @@ interface ChatWidgetConfig {
62
62
  */
63
63
  persist?: boolean;
64
64
  onNavigate?: (path: string) => void;
65
+ /**
66
+ * Hide the close (X) button in the widget header.
67
+ * Useful when the integrator controls open/close from an outer bubble wrapper
68
+ * and wants to avoid showing two close buttons.
69
+ */
70
+ hideCloseButton?: boolean;
65
71
  source?: string;
66
72
  userContext?: UserContext;
67
73
  /** Per-session tool overrides used by Playground (not persisted) */
@@ -79,7 +85,36 @@ interface ChatWidgetProps extends ChatWidgetConfig {
79
85
  declare function getContrastColor(hex: string): string;
80
86
  declare function formatToolName(name: string): string;
81
87
 
82
- declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
88
+ interface BubbleWidgetProps extends ChatWidgetConfig {
89
+ /** Where the bubble is anchored. Default: "bottom-right" */
90
+ position?: "bottom-right" | "bottom-left";
91
+ /** Width of the chat panel in pixels. Default: 360 */
92
+ width?: number;
93
+ /** Height of the chat panel in pixels. Default: 580 */
94
+ height?: number;
95
+ /** Width of the expanded panel in pixels. Default: 640 */
96
+ expandedWidth?: number;
97
+ /** Height of the expanded panel. Default: "calc(100vh - 100px)" */
98
+ expandedHeight?: string;
99
+ /** Enable Cmd/Ctrl + shortcutKey to toggle the widget. Default: false */
100
+ keyboardShortcut?: boolean;
101
+ /** Key to use with Cmd/Ctrl. Default: "k" */
102
+ shortcutKey?: string;
103
+ /** Open the widget automatically on mount. Default: false */
104
+ autoOpen?: boolean;
105
+ /**
106
+ * Image URL to show inside the bubble button.
107
+ * Falls back to the Wallavi logo if not provided.
108
+ */
109
+ bubbleIconUrl?: string;
110
+ /** Size of the bubble toggle button in pixels. Default: 52 */
111
+ bubbleSize?: number;
112
+ /** className applied to the chat panel */
113
+ panelClassName?: string;
114
+ }
115
+ declare function BubbleWidget({ position, width, height, expandedWidth, expandedHeight, keyboardShortcut, shortcutKey, autoOpen, bubbleIconUrl, bubbleSize, panelClassName, ...chatProps }: BubbleWidgetProps): react_jsx_runtime.JSX.Element;
116
+
117
+ 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;
83
118
 
84
119
  interface UseChatOptions {
85
120
  agentId: string;
@@ -106,4 +141,4 @@ interface UseChatReturn {
106
141
  }
107
142
  declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
108
143
 
109
- export { ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type Message, type MessagePart, type ToolPart, type UserContext, formatToolName, getContrastColor, useChat };
144
+ export { BubbleWidget, type BubbleWidgetProps, ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type Message, type MessagePart, type PageContext, type ToolPart, type UserContext, formatToolName, getContrastColor, useChat };
package/dist/index.d.ts CHANGED
@@ -62,6 +62,12 @@ interface ChatWidgetConfig {
62
62
  */
63
63
  persist?: boolean;
64
64
  onNavigate?: (path: string) => void;
65
+ /**
66
+ * Hide the close (X) button in the widget header.
67
+ * Useful when the integrator controls open/close from an outer bubble wrapper
68
+ * and wants to avoid showing two close buttons.
69
+ */
70
+ hideCloseButton?: boolean;
65
71
  source?: string;
66
72
  userContext?: UserContext;
67
73
  /** Per-session tool overrides used by Playground (not persisted) */
@@ -79,7 +85,36 @@ interface ChatWidgetProps extends ChatWidgetConfig {
79
85
  declare function getContrastColor(hex: string): string;
80
86
  declare function formatToolName(name: string): string;
81
87
 
82
- declare function ChatWidget({ agentId, workspaceId, agentName, displayName, profilePicture, userMessageColor, initialMessages, suggestedMessages, messagePlaceholder, watermark, watermarkLogoUrl, footer, theme, showThinking, regenerateMessage, persist, onNavigate, source, userContext, playgroundOverrides, className, onClose, onReset, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
88
+ interface BubbleWidgetProps extends ChatWidgetConfig {
89
+ /** Where the bubble is anchored. Default: "bottom-right" */
90
+ position?: "bottom-right" | "bottom-left";
91
+ /** Width of the chat panel in pixels. Default: 360 */
92
+ width?: number;
93
+ /** Height of the chat panel in pixels. Default: 580 */
94
+ height?: number;
95
+ /** Width of the expanded panel in pixels. Default: 640 */
96
+ expandedWidth?: number;
97
+ /** Height of the expanded panel. Default: "calc(100vh - 100px)" */
98
+ expandedHeight?: string;
99
+ /** Enable Cmd/Ctrl + shortcutKey to toggle the widget. Default: false */
100
+ keyboardShortcut?: boolean;
101
+ /** Key to use with Cmd/Ctrl. Default: "k" */
102
+ shortcutKey?: string;
103
+ /** Open the widget automatically on mount. Default: false */
104
+ autoOpen?: boolean;
105
+ /**
106
+ * Image URL to show inside the bubble button.
107
+ * Falls back to the Wallavi logo if not provided.
108
+ */
109
+ bubbleIconUrl?: string;
110
+ /** Size of the bubble toggle button in pixels. Default: 52 */
111
+ bubbleSize?: number;
112
+ /** className applied to the chat panel */
113
+ panelClassName?: string;
114
+ }
115
+ declare function BubbleWidget({ position, width, height, expandedWidth, expandedHeight, keyboardShortcut, shortcutKey, autoOpen, bubbleIconUrl, bubbleSize, panelClassName, ...chatProps }: BubbleWidgetProps): react_jsx_runtime.JSX.Element;
116
+
117
+ 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;
83
118
 
84
119
  interface UseChatOptions {
85
120
  agentId: string;
@@ -106,4 +141,4 @@ interface UseChatReturn {
106
141
  }
107
142
  declare function useChat({ agentId, workspaceId, source, userContext, persist, onNavigate, playgroundOverrides, }: UseChatOptions): UseChatReturn;
108
143
 
109
- export { ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type Message, type MessagePart, type ToolPart, type UserContext, formatToolName, getContrastColor, useChat };
144
+ export { BubbleWidget, type BubbleWidgetProps, ChatWidget, type ChatWidgetConfig, type ChatWidgetProps, type Message, type MessagePart, type PageContext, type ToolPart, type UserContext, formatToolName, getContrastColor, useChat };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var react = require('react');
3
4
  var clsx = require('clsx');
4
5
  var tailwindMerge = require('tailwind-merge');
5
- var react = require('react');
6
6
  var lucideReact = require('lucide-react');
7
7
  var AvatarPrimitive = require('@radix-ui/react-avatar');
8
8
  var jsxRuntime = require('react/jsx-runtime');
@@ -33,7 +33,7 @@ var AvatarPrimitive__namespace = /*#__PURE__*/_interopNamespace(AvatarPrimitive)
33
33
  var ReactMarkdownLib__default = /*#__PURE__*/_interopDefault(ReactMarkdownLib);
34
34
  var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
35
35
 
36
- // src/chat-widget.tsx
36
+ // src/bubble-widget.tsx
37
37
 
38
38
  // src/types.ts
39
39
  function getContrastColor(hex) {
@@ -615,13 +615,14 @@ function ChatWidget({
615
615
  suggestedMessages = [],
616
616
  messagePlaceholder,
617
617
  watermark = true,
618
- watermarkLogoUrl = "https://wallavi.com/wallavi.svg",
618
+ watermarkLogoUrl = "https://app.wallavi.com/wallavi.svg",
619
619
  footer,
620
620
  theme,
621
621
  showThinking = false,
622
622
  regenerateMessage = false,
623
623
  persist = false,
624
624
  onNavigate,
625
+ hideCloseButton = false,
625
626
  source = "playground",
626
627
  userContext,
627
628
  playgroundOverrides,
@@ -642,7 +643,7 @@ function ChatWidget({
642
643
  "div",
643
644
  {
644
645
  className: cn3(
645
- "flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background",
646
+ "flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
646
647
  className
647
648
  ),
648
649
  style: { colorScheme: theme },
@@ -655,7 +656,7 @@ function ChatWidget({
655
656
  headerBg,
656
657
  headerText,
657
658
  onReset: handleReset,
658
- onClose
659
+ onClose: hideCloseButton ? void 0 : onClose
659
660
  }
660
661
  ),
661
662
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -708,7 +709,215 @@ function ChatWidget({
708
709
  }
709
710
  );
710
711
  }
712
+ var cn4 = (...inputs) => tailwindMerge.twMerge(clsx.clsx(inputs));
713
+ function DefaultIcon() {
714
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
715
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "12", fill: "currentColor", opacity: 0.12 }),
716
+ /* @__PURE__ */ jsxRuntime.jsx(
717
+ "path",
718
+ {
719
+ 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",
720
+ fill: "currentColor"
721
+ }
722
+ )
723
+ ] });
724
+ }
725
+ function CloseIcon() {
726
+ 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" }) });
727
+ }
728
+ function ExpandIcon() {
729
+ 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" }) });
730
+ }
731
+ function MinimizeIcon() {
732
+ 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" }) });
733
+ }
734
+ function BubbleWidget({
735
+ position = "bottom-right",
736
+ width = 360,
737
+ height = 580,
738
+ expandedWidth = 640,
739
+ expandedHeight = "calc(100vh - 100px)",
740
+ keyboardShortcut = false,
741
+ shortcutKey = "k",
742
+ autoOpen = false,
743
+ bubbleIconUrl,
744
+ bubbleSize = 52,
745
+ panelClassName,
746
+ // ChatWidgetConfig props
747
+ ...chatProps
748
+ }) {
749
+ const [open, setOpen] = react.useState(false);
750
+ const [expanded, setExpanded] = react.useState(false);
751
+ const panelRef = react.useRef(null);
752
+ const autoOpenedRef = react.useRef(false);
753
+ react.useEffect(() => {
754
+ if (!autoOpen || autoOpenedRef.current) return;
755
+ const dismissed = sessionStorage.getItem("wallavi_bubble_dismissed");
756
+ if (!dismissed) {
757
+ autoOpenedRef.current = true;
758
+ setOpen(true);
759
+ }
760
+ }, [autoOpen]);
761
+ react.useEffect(() => {
762
+ if (!keyboardShortcut) return;
763
+ const handler = (e) => {
764
+ if ((e.metaKey || e.ctrlKey) && e.key === shortcutKey) {
765
+ e.preventDefault();
766
+ setOpen((v) => !v);
767
+ }
768
+ };
769
+ window.addEventListener("keydown", handler);
770
+ return () => window.removeEventListener("keydown", handler);
771
+ }, [keyboardShortcut, shortcutKey]);
772
+ react.useEffect(() => {
773
+ if (!open) return;
774
+ const id = setTimeout(() => {
775
+ panelRef.current?.querySelector("textarea")?.focus();
776
+ }, 50);
777
+ return () => clearTimeout(id);
778
+ }, [open]);
779
+ const handleClose = () => {
780
+ setOpen(false);
781
+ sessionStorage.setItem("wallavi_bubble_dismissed", "1");
782
+ };
783
+ const toggleExpanded = () => setExpanded((v) => !v);
784
+ const isLeft = position === "bottom-left";
785
+ return /* @__PURE__ */ jsxRuntime.jsxs(
786
+ "div",
787
+ {
788
+ style: {
789
+ position: "fixed",
790
+ bottom: 20,
791
+ [isLeft ? "left" : "right"]: 20,
792
+ zIndex: 9999,
793
+ display: "flex",
794
+ flexDirection: "column",
795
+ alignItems: isLeft ? "flex-start" : "flex-end",
796
+ gap: 12
797
+ },
798
+ children: [
799
+ /* @__PURE__ */ jsxRuntime.jsxs(
800
+ "div",
801
+ {
802
+ ref: panelRef,
803
+ "aria-hidden": !open,
804
+ style: { display: open ? "block" : "none", position: "relative" },
805
+ children: [
806
+ /* @__PURE__ */ jsxRuntime.jsxs(
807
+ "div",
808
+ {
809
+ style: {
810
+ position: "absolute",
811
+ top: -12,
812
+ [isLeft ? "left" : "right"]: -12,
813
+ zIndex: 10,
814
+ display: "flex",
815
+ alignItems: "center",
816
+ gap: 4
817
+ },
818
+ children: [
819
+ /* @__PURE__ */ jsxRuntime.jsx(
820
+ "button",
821
+ {
822
+ onClick: toggleExpanded,
823
+ title: expanded ? "Minimize" : "Expand",
824
+ style: {
825
+ width: 24,
826
+ height: 24,
827
+ borderRadius: "50%",
828
+ border: "1px solid rgba(0,0,0,0.1)",
829
+ background: "var(--background, #fff)",
830
+ boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
831
+ display: "flex",
832
+ alignItems: "center",
833
+ justifyContent: "center",
834
+ cursor: "pointer",
835
+ color: "var(--muted-foreground, #888)"
836
+ },
837
+ children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(ExpandIcon, {})
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsxRuntime.jsx(
841
+ "button",
842
+ {
843
+ onClick: handleClose,
844
+ title: "Close",
845
+ style: {
846
+ width: 24,
847
+ height: 24,
848
+ borderRadius: "50%",
849
+ border: "1px solid rgba(0,0,0,0.1)",
850
+ background: "var(--background, #fff)",
851
+ boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
852
+ display: "flex",
853
+ alignItems: "center",
854
+ justifyContent: "center",
855
+ cursor: "pointer",
856
+ color: "var(--muted-foreground, #888)"
857
+ },
858
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
859
+ }
860
+ )
861
+ ]
862
+ }
863
+ ),
864
+ /* @__PURE__ */ jsxRuntime.jsx(
865
+ "div",
866
+ {
867
+ style: {
868
+ width: expanded ? Math.min(expandedWidth, typeof window !== "undefined" ? window.innerWidth - 40 : expandedWidth) : width,
869
+ height: expanded ? expandedHeight : height,
870
+ transition: "width 0.3s ease, height 0.3s ease"
871
+ },
872
+ children: /* @__PURE__ */ jsxRuntime.jsx(
873
+ ChatWidget,
874
+ {
875
+ ...chatProps,
876
+ hideCloseButton: true,
877
+ className: cn4("shadow-2xl h-full", panelClassName)
878
+ }
879
+ )
880
+ }
881
+ )
882
+ ]
883
+ }
884
+ ),
885
+ /* @__PURE__ */ jsxRuntime.jsx(
886
+ "button",
887
+ {
888
+ onClick: () => setOpen((v) => !v),
889
+ title: open ? "Close chat" : "Open chat",
890
+ style: {
891
+ width: bubbleSize,
892
+ height: bubbleSize,
893
+ borderRadius: "50%",
894
+ border: "1px solid rgba(0,0,0,0.1)",
895
+ boxShadow: "0 4px 16px rgba(0,0,0,0.16)",
896
+ cursor: "pointer",
897
+ overflow: "hidden",
898
+ display: "flex",
899
+ alignItems: "center",
900
+ justifyContent: "center",
901
+ transition: "transform 0.2s ease, box-shadow 0.2s ease",
902
+ background: open ? "var(--foreground, #19191c)" : "var(--background, #fff)",
903
+ color: open ? "var(--background, #fff)" : "var(--foreground, #19191c)"
904
+ },
905
+ children: open ? /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) : bubbleIconUrl ? /* @__PURE__ */ jsxRuntime.jsx(
906
+ "img",
907
+ {
908
+ src: bubbleIconUrl,
909
+ alt: "Open chat",
910
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
911
+ }
912
+ ) : /* @__PURE__ */ jsxRuntime.jsx(DefaultIcon, {})
913
+ }
914
+ )
915
+ ]
916
+ }
917
+ );
918
+ }
711
919
 
920
+ exports.BubbleWidget = BubbleWidget;
712
921
  exports.ChatWidget = ChatWidget;
713
922
  exports.formatToolName = formatToolName;
714
923
  exports.getContrastColor = getContrastColor;
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
1
2
  import { clsx } from 'clsx';
2
3
  import { twMerge } from 'tailwind-merge';
3
- import { useState, useRef, useEffect, useCallback } from 'react';
4
4
  import { RotateCcw, X, Loader2, ArrowUp, Zap, ChevronDown, CheckCircle2, AlertCircle } from 'lucide-react';
5
5
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import ReactMarkdownLib from 'react-markdown';
8
8
  import remarkGfm from 'remark-gfm';
9
9
 
10
- // src/chat-widget.tsx
10
+ // src/bubble-widget.tsx
11
11
 
12
12
  // src/types.ts
13
13
  function getContrastColor(hex) {
@@ -589,13 +589,14 @@ function ChatWidget({
589
589
  suggestedMessages = [],
590
590
  messagePlaceholder,
591
591
  watermark = true,
592
- watermarkLogoUrl = "https://wallavi.com/wallavi.svg",
592
+ watermarkLogoUrl = "https://app.wallavi.com/wallavi.svg",
593
593
  footer,
594
594
  theme,
595
595
  showThinking = false,
596
596
  regenerateMessage = false,
597
597
  persist = false,
598
598
  onNavigate,
599
+ hideCloseButton = false,
599
600
  source = "playground",
600
601
  userContext,
601
602
  playgroundOverrides,
@@ -616,7 +617,7 @@ function ChatWidget({
616
617
  "div",
617
618
  {
618
619
  className: cn3(
619
- "flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background",
620
+ "flex flex-col overflow-hidden rounded-2xl border shadow-xl bg-background h-full",
620
621
  className
621
622
  ),
622
623
  style: { colorScheme: theme },
@@ -629,7 +630,7 @@ function ChatWidget({
629
630
  headerBg,
630
631
  headerText,
631
632
  onReset: handleReset,
632
- onClose
633
+ onClose: hideCloseButton ? void 0 : onClose
633
634
  }
634
635
  ),
635
636
  /* @__PURE__ */ jsx(
@@ -682,5 +683,212 @@ function ChatWidget({
682
683
  }
683
684
  );
684
685
  }
686
+ var cn4 = (...inputs) => twMerge(clsx(inputs));
687
+ function DefaultIcon() {
688
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", style: { width: 26, height: 26 }, children: [
689
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "12", fill: "currentColor", opacity: 0.12 }),
690
+ /* @__PURE__ */ jsx(
691
+ "path",
692
+ {
693
+ 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",
694
+ fill: "currentColor"
695
+ }
696
+ )
697
+ ] });
698
+ }
699
+ function CloseIcon() {
700
+ 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" }) });
701
+ }
702
+ function ExpandIcon() {
703
+ 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" }) });
704
+ }
705
+ function MinimizeIcon() {
706
+ 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" }) });
707
+ }
708
+ function BubbleWidget({
709
+ position = "bottom-right",
710
+ width = 360,
711
+ height = 580,
712
+ expandedWidth = 640,
713
+ expandedHeight = "calc(100vh - 100px)",
714
+ keyboardShortcut = false,
715
+ shortcutKey = "k",
716
+ autoOpen = false,
717
+ bubbleIconUrl,
718
+ bubbleSize = 52,
719
+ panelClassName,
720
+ // ChatWidgetConfig props
721
+ ...chatProps
722
+ }) {
723
+ const [open, setOpen] = useState(false);
724
+ const [expanded, setExpanded] = useState(false);
725
+ const panelRef = useRef(null);
726
+ const autoOpenedRef = useRef(false);
727
+ useEffect(() => {
728
+ if (!autoOpen || autoOpenedRef.current) return;
729
+ const dismissed = sessionStorage.getItem("wallavi_bubble_dismissed");
730
+ if (!dismissed) {
731
+ autoOpenedRef.current = true;
732
+ setOpen(true);
733
+ }
734
+ }, [autoOpen]);
735
+ useEffect(() => {
736
+ if (!keyboardShortcut) return;
737
+ const handler = (e) => {
738
+ if ((e.metaKey || e.ctrlKey) && e.key === shortcutKey) {
739
+ e.preventDefault();
740
+ setOpen((v) => !v);
741
+ }
742
+ };
743
+ window.addEventListener("keydown", handler);
744
+ return () => window.removeEventListener("keydown", handler);
745
+ }, [keyboardShortcut, shortcutKey]);
746
+ useEffect(() => {
747
+ if (!open) return;
748
+ const id = setTimeout(() => {
749
+ panelRef.current?.querySelector("textarea")?.focus();
750
+ }, 50);
751
+ return () => clearTimeout(id);
752
+ }, [open]);
753
+ const handleClose = () => {
754
+ setOpen(false);
755
+ sessionStorage.setItem("wallavi_bubble_dismissed", "1");
756
+ };
757
+ const toggleExpanded = () => setExpanded((v) => !v);
758
+ const isLeft = position === "bottom-left";
759
+ return /* @__PURE__ */ jsxs(
760
+ "div",
761
+ {
762
+ style: {
763
+ position: "fixed",
764
+ bottom: 20,
765
+ [isLeft ? "left" : "right"]: 20,
766
+ zIndex: 9999,
767
+ display: "flex",
768
+ flexDirection: "column",
769
+ alignItems: isLeft ? "flex-start" : "flex-end",
770
+ gap: 12
771
+ },
772
+ children: [
773
+ /* @__PURE__ */ jsxs(
774
+ "div",
775
+ {
776
+ ref: panelRef,
777
+ "aria-hidden": !open,
778
+ style: { display: open ? "block" : "none", position: "relative" },
779
+ children: [
780
+ /* @__PURE__ */ jsxs(
781
+ "div",
782
+ {
783
+ style: {
784
+ position: "absolute",
785
+ top: -12,
786
+ [isLeft ? "left" : "right"]: -12,
787
+ zIndex: 10,
788
+ display: "flex",
789
+ alignItems: "center",
790
+ gap: 4
791
+ },
792
+ children: [
793
+ /* @__PURE__ */ jsx(
794
+ "button",
795
+ {
796
+ onClick: toggleExpanded,
797
+ title: expanded ? "Minimize" : "Expand",
798
+ style: {
799
+ width: 24,
800
+ height: 24,
801
+ borderRadius: "50%",
802
+ border: "1px solid rgba(0,0,0,0.1)",
803
+ background: "var(--background, #fff)",
804
+ boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
805
+ display: "flex",
806
+ alignItems: "center",
807
+ justifyContent: "center",
808
+ cursor: "pointer",
809
+ color: "var(--muted-foreground, #888)"
810
+ },
811
+ children: expanded ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(ExpandIcon, {})
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsx(
815
+ "button",
816
+ {
817
+ onClick: handleClose,
818
+ title: "Close",
819
+ style: {
820
+ width: 24,
821
+ height: 24,
822
+ borderRadius: "50%",
823
+ border: "1px solid rgba(0,0,0,0.1)",
824
+ background: "var(--background, #fff)",
825
+ boxShadow: "0 1px 4px rgba(0,0,0,0.12)",
826
+ display: "flex",
827
+ alignItems: "center",
828
+ justifyContent: "center",
829
+ cursor: "pointer",
830
+ color: "var(--muted-foreground, #888)"
831
+ },
832
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
833
+ }
834
+ )
835
+ ]
836
+ }
837
+ ),
838
+ /* @__PURE__ */ jsx(
839
+ "div",
840
+ {
841
+ style: {
842
+ width: expanded ? Math.min(expandedWidth, typeof window !== "undefined" ? window.innerWidth - 40 : expandedWidth) : width,
843
+ height: expanded ? expandedHeight : height,
844
+ transition: "width 0.3s ease, height 0.3s ease"
845
+ },
846
+ children: /* @__PURE__ */ jsx(
847
+ ChatWidget,
848
+ {
849
+ ...chatProps,
850
+ hideCloseButton: true,
851
+ className: cn4("shadow-2xl h-full", panelClassName)
852
+ }
853
+ )
854
+ }
855
+ )
856
+ ]
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx(
860
+ "button",
861
+ {
862
+ onClick: () => setOpen((v) => !v),
863
+ title: open ? "Close chat" : "Open chat",
864
+ style: {
865
+ width: bubbleSize,
866
+ height: bubbleSize,
867
+ borderRadius: "50%",
868
+ border: "1px solid rgba(0,0,0,0.1)",
869
+ boxShadow: "0 4px 16px rgba(0,0,0,0.16)",
870
+ cursor: "pointer",
871
+ overflow: "hidden",
872
+ display: "flex",
873
+ alignItems: "center",
874
+ justifyContent: "center",
875
+ transition: "transform 0.2s ease, box-shadow 0.2s ease",
876
+ background: open ? "var(--foreground, #19191c)" : "var(--background, #fff)",
877
+ color: open ? "var(--background, #fff)" : "var(--foreground, #19191c)"
878
+ },
879
+ children: open ? /* @__PURE__ */ jsx(CloseIcon, {}) : bubbleIconUrl ? /* @__PURE__ */ jsx(
880
+ "img",
881
+ {
882
+ src: bubbleIconUrl,
883
+ alt: "Open chat",
884
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
885
+ }
886
+ ) : /* @__PURE__ */ jsx(DefaultIcon, {})
887
+ }
888
+ )
889
+ ]
890
+ }
891
+ );
892
+ }
685
893
 
686
- export { ChatWidget, formatToolName, getContrastColor, useChat };
894
+ export { BubbleWidget, ChatWidget, formatToolName, getContrastColor, useChat };
package/package.json CHANGED
@@ -11,42 +11,32 @@
11
11
  "@types/node": "^22.10.7",
12
12
  "@types/react": "^19.1.0",
13
13
  "@types/react-dom": "^19.1.0",
14
- "@wallavi/typescript-config": "workspace:*",
15
14
  "tsup": "^8.3.5",
16
- "typescript": "^5.7.3"
15
+ "typescript": "^5.7.3",
16
+ "@wallavi/typescript-config": "0.0.1"
17
17
  },
18
18
  "exports": {
19
19
  ".": {
20
- "default": "./src/index.ts",
21
- "types": "./src/index.ts"
20
+ "import": "./dist/index.mjs",
21
+ "require": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
22
23
  }
23
24
  },
24
25
  "files": [
25
26
  "dist"
26
27
  ],
27
- "main": "./src/index.ts",
28
+ "main": "./dist/index.js",
28
29
  "name": "@wallavi/widget",
29
30
  "peerDependencies": {
30
31
  "react": "^18.0.0 || ^19.0.0",
31
32
  "react-dom": "^18.0.0 || ^19.0.0"
32
33
  },
33
34
  "private": false,
34
- "publishConfig": {
35
- "exports": {
36
- ".": {
37
- "import": "./dist/index.mjs",
38
- "require": "./dist/index.js",
39
- "types": "./dist/index.d.ts"
40
- }
41
- },
42
- "main": "./dist/index.js",
43
- "module": "./dist/index.mjs",
44
- "types": "./dist/index.d.ts"
45
- },
35
+ "types": "./dist/index.d.ts",
36
+ "version": "1.2.0",
46
37
  "scripts": {
47
38
  "build": "tsup",
48
39
  "typecheck": "tsc --noEmit"
49
40
  },
50
- "types": "./src/index.ts",
51
- "version": "1.1.0"
52
- }
41
+ "module": "./dist/index.mjs"
42
+ }