@emblemvault/hustle-react 1.2.0 → 1.4.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/README.md CHANGED
@@ -111,6 +111,62 @@ Complete chat UI component.
111
111
  />
112
112
  ```
113
113
 
114
+ ### HustleChatWidget
115
+
116
+ Floating chat widget for site-wide chatbot integration. Perfect for customer support or AI assistants that persist across page navigations.
117
+
118
+ ```tsx
119
+ import { HustleProvider, HustleChatWidget } from '@emblemvault/hustle-react';
120
+
121
+ // Add to your Next.js layout for site-wide availability
122
+ export default function RootLayout({ children }) {
123
+ return (
124
+ <html>
125
+ <body>
126
+ <HustleProvider>
127
+ {children}
128
+ <HustleChatWidget
129
+ config={{
130
+ position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
131
+ size: 'md', // 'sm' | 'md' | 'lg' | 'xl' | 'full'
132
+ title: 'Support', // Header title
133
+ defaultOpen: false, // Start open?
134
+ offset: { x: 24, y: 24 }, // Edge offset in pixels
135
+ storageKey: 'chat-open', // localStorage key for persistence (false to disable)
136
+ showBadge: true, // Show notification badge
137
+ badgeContent: 3, // Badge content
138
+ onOpen: () => {}, // Called when widget opens
139
+ onClose: () => {}, // Called when widget closes
140
+ }}
141
+ placeholder="How can we help?"
142
+ showSettings
143
+ />
144
+ </HustleProvider>
145
+ </body>
146
+ </html>
147
+ );
148
+ }
149
+ ```
150
+
151
+ #### Widget Configuration
152
+
153
+ | Option | Type | Default | Description |
154
+ |--------|------|---------|-------------|
155
+ | `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Screen position |
156
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Chat panel size |
157
+ | `title` | `string` | `'Chat'` | Panel header title |
158
+ | `defaultOpen` | `boolean` | `false` | Whether widget starts open |
159
+ | `offset` | `{ x: number, y: number }` | `{ x: 24, y: 24 }` | Offset from screen edge |
160
+ | `storageKey` | `string \| false` | `'hustle-widget-open'` | Key for persisting state |
161
+ | `showBadge` | `boolean` | `false` | Show notification badge |
162
+ | `badgeContent` | `string \| number` | - | Badge content |
163
+ | `launcherIcon` | `React.ReactNode` | Chat bubble | Custom launcher icon |
164
+ | `launcherStyle` | `React.CSSProperties` | - | Custom launcher styles |
165
+ | `panelStyle` | `React.CSSProperties` | - | Custom panel styles |
166
+ | `zIndex` | `number` | `9999` | Widget z-index |
167
+ | `onOpen` | `() => void` | - | Called when widget opens |
168
+ | `onClose` | `() => void` | - | Called when widget closes |
169
+
114
170
  ## Plugins
115
171
 
116
172
  Extend the AI with custom tools.
@@ -3547,7 +3547,7 @@ var screenshotExecutor = async (args2) => {
3547
3547
  if (!window.html2canvas) {
3548
3548
  await new Promise((resolve, reject) => {
3549
3549
  const script = document.createElement("script");
3550
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
3550
+ script.src = "https://cdn.jsdelivr.net/npm/html2canvas-pro@1.5.0/dist/html2canvas-pro.min.js";
3551
3551
  script.onload = () => resolve();
3552
3552
  script.onerror = () => reject(new Error("Failed to load html2canvas"));
3553
3553
  document.head.appendChild(script);
@@ -4605,6 +4605,14 @@ var animations = `
4605
4605
  text-shadow: 0 0 8px ${defaults.colors.accentPrimary};
4606
4606
  }
4607
4607
  }
4608
+ /* Hide scrollbar while maintaining scroll functionality */
4609
+ .hustle-hide-scrollbar {
4610
+ -ms-overflow-style: none; /* IE and Edge */
4611
+ scrollbar-width: none; /* Firefox */
4612
+ }
4613
+ .hustle-hide-scrollbar::-webkit-scrollbar {
4614
+ display: none; /* Chrome, Safari, Opera */
4615
+ }
4608
4616
  `;
4609
4617
 
4610
4618
  // ../../node_modules/marked/lib/marked.esm.js
@@ -14862,7 +14870,11 @@ var styles = {
14862
14870
  border: "none",
14863
14871
  borderRadius: 0,
14864
14872
  color: tokens.colors.textTertiary,
14865
- flexShrink: 0
14873
+ flexShrink: 0,
14874
+ display: "flex",
14875
+ alignItems: "center",
14876
+ justifyContent: "center",
14877
+ cursor: "pointer"
14866
14878
  },
14867
14879
  inputWrapper: {
14868
14880
  flex: 1
@@ -15071,6 +15083,7 @@ function HustleChat({
15071
15083
  placeholder = "Type a message...",
15072
15084
  showSettings = false,
15073
15085
  showDebug = false,
15086
+ hideHeader = false,
15074
15087
  initialSystemPrompt = "",
15075
15088
  onMessage,
15076
15089
  onToolCall,
@@ -15083,6 +15096,7 @@ function HustleChat({
15083
15096
  isLoading,
15084
15097
  error: error2,
15085
15098
  models,
15099
+ client,
15086
15100
  chatStream,
15087
15101
  uploadFile,
15088
15102
  selectedModel,
@@ -15109,6 +15123,7 @@ function HustleChat({
15109
15123
  const [showSettingsPanel, setShowSettingsPanel] = useState(false);
15110
15124
  const messagesEndRef = useRef(null);
15111
15125
  const fileInputRef = useRef(null);
15126
+ const messagesRef = useRef(messages);
15112
15127
  useEffect(() => {
15113
15128
  if (initialSystemPrompt && !systemPrompt) {
15114
15129
  setSystemPrompt(initialSystemPrompt);
@@ -15117,6 +15132,9 @@ function HustleChat({
15117
15132
  useEffect(() => {
15118
15133
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
15119
15134
  }, [messages]);
15135
+ useEffect(() => {
15136
+ messagesRef.current = messages;
15137
+ }, [messages]);
15120
15138
  const handleFileSelect = useCallback(
15121
15139
  async (e) => {
15122
15140
  const files = e.target.files;
@@ -15138,6 +15156,96 @@ function HustleChat({
15138
15156
  const removeAttachment = useCallback((index) => {
15139
15157
  setAttachments((prev) => prev.filter((_2, i) => i !== index));
15140
15158
  }, []);
15159
+ const sendContinue = useCallback(async () => {
15160
+ if (isStreaming || !isReady) return;
15161
+ console.log("[AUTO_CONTINUE] Sending continue message...");
15162
+ const userMessage = {
15163
+ id: generateId(),
15164
+ role: "user",
15165
+ content: "continue"
15166
+ };
15167
+ setMessages((prev) => [...prev, userMessage]);
15168
+ onMessage?.(userMessage);
15169
+ const assistantMessage = {
15170
+ id: generateId(),
15171
+ role: "assistant",
15172
+ content: "",
15173
+ isStreaming: true,
15174
+ toolCalls: []
15175
+ };
15176
+ setMessages((prev) => [...prev, assistantMessage]);
15177
+ setIsStreaming(true);
15178
+ setCurrentToolCalls([]);
15179
+ try {
15180
+ const chatMessages = messagesRef.current.filter((m2) => !m2.isStreaming).map((m2) => ({ role: m2.role, content: m2.content }));
15181
+ chatMessages.push({ role: "user", content: "continue" });
15182
+ const stream = chatStream({
15183
+ messages: chatMessages,
15184
+ processChunks: true
15185
+ });
15186
+ let fullContent = "";
15187
+ const toolCallsAccumulated = [];
15188
+ for await (const chunk of stream) {
15189
+ if (chunk.type === "text") {
15190
+ fullContent += chunk.value;
15191
+ setMessages(
15192
+ (prev) => prev.map(
15193
+ (m2) => m2.id === assistantMessage.id ? { ...m2, content: fullContent } : m2
15194
+ )
15195
+ );
15196
+ } else if (chunk.type === "tool_call") {
15197
+ const toolCall = chunk.value;
15198
+ toolCallsAccumulated.push(toolCall);
15199
+ setCurrentToolCalls([...toolCallsAccumulated]);
15200
+ setMessages(
15201
+ (prev) => prev.map(
15202
+ (m2) => m2.id === assistantMessage.id ? { ...m2, toolCalls: [...toolCallsAccumulated] } : m2
15203
+ )
15204
+ );
15205
+ onToolCall?.(toolCall);
15206
+ } else if (chunk.type === "error") {
15207
+ console.error("Stream error:", chunk.value);
15208
+ }
15209
+ }
15210
+ const processedResponse = await stream.response;
15211
+ const finalContent = processedResponse?.content || fullContent || "(No response)";
15212
+ setMessages(
15213
+ (prev) => prev.map(
15214
+ (m2) => m2.id === assistantMessage.id ? { ...m2, isStreaming: false, content: finalContent } : m2
15215
+ )
15216
+ );
15217
+ onResponse?.(finalContent);
15218
+ } catch (err) {
15219
+ console.error("Continue error:", err);
15220
+ setMessages(
15221
+ (prev) => prev.map(
15222
+ (m2) => m2.id === assistantMessage.id ? { ...m2, isStreaming: false, content: `Error: ${err instanceof Error ? err.message : "Unknown error"}` } : m2
15223
+ )
15224
+ );
15225
+ } finally {
15226
+ setIsStreaming(false);
15227
+ setCurrentToolCalls([]);
15228
+ }
15229
+ }, [isStreaming, isReady, chatStream, onMessage, onToolCall, onResponse]);
15230
+ useEffect(() => {
15231
+ if (!client) return;
15232
+ const unsubMaxTools = client.on("max_tools_reached", (event) => {
15233
+ console.log(`[AUTO_CONTINUE] Max tools reached (${event.toolsExecuted}/${event.maxSteps}), auto-continuing...`);
15234
+ setTimeout(() => {
15235
+ sendContinue();
15236
+ }, 100);
15237
+ });
15238
+ const unsubTimeout = client.on("timeout", (event) => {
15239
+ console.log(`[AUTO_CONTINUE] Timeout: ${event.message}, auto-continuing...`);
15240
+ setTimeout(() => {
15241
+ sendContinue();
15242
+ }, 100);
15243
+ });
15244
+ return () => {
15245
+ unsubMaxTools();
15246
+ unsubTimeout();
15247
+ };
15248
+ }, [client, sendContinue]);
15141
15249
  const sendMessage = useCallback(async () => {
15142
15250
  const content = inputValue.trim();
15143
15251
  if (!content || isStreaming || !isReady) return;
@@ -15234,7 +15342,7 @@ function HustleChat({
15234
15342
  return /* @__PURE__ */ jsxs(Fragment, { children: [
15235
15343
  /* @__PURE__ */ jsx("style", { children: animations }),
15236
15344
  /* @__PURE__ */ jsxs("div", { className, style: styles.container, children: [
15237
- /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
15345
+ !hideHeader && /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
15238
15346
  /* @__PURE__ */ jsx("h2", { style: styles.headerTitle, children: "Chat" }),
15239
15347
  /* @__PURE__ */ jsxs("div", { style: styles.headerActions, children: [
15240
15348
  selectedModel && /* @__PURE__ */ jsx("span", { style: { fontSize: tokens.typography.fontSizeSm, color: tokens.colors.textSecondary }, children: selectedModel.split("/").pop() }),
@@ -15409,7 +15517,7 @@ function HustleChat({
15409
15517
  ] })
15410
15518
  ] })
15411
15519
  ] }) }),
15412
- /* @__PURE__ */ jsxs("div", { style: styles.messagesArea, children: [
15520
+ /* @__PURE__ */ jsxs("div", { className: "hustle-hide-scrollbar", style: styles.messagesArea, children: [
15413
15521
  messages.length === 0 && /* @__PURE__ */ jsx("div", { style: styles.messagesEmpty, children: /* @__PURE__ */ jsx("p", { children: getPlaceholderMessage() }) }),
15414
15522
  /* @__PURE__ */ jsx("div", { style: styles.messagesContainer, children: messages.map((message) => /* @__PURE__ */ jsx(
15415
15523
  MessageBubble,
@@ -15536,6 +15644,343 @@ function SettingsIcon() {
15536
15644
  function AttachIcon() {
15537
15645
  return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
15538
15646
  }
15647
+ var sizeConfigs = {
15648
+ sm: { width: "320px", height: "400px" },
15649
+ md: { width: "380px", height: "520px" },
15650
+ lg: { width: "420px", height: "600px" },
15651
+ xl: { width: "480px", height: "700px" },
15652
+ full: { width: "100vw", height: "100vh" }
15653
+ };
15654
+ var widgetStyles = {
15655
+ // Container for absolute positioning
15656
+ container: {
15657
+ position: "fixed",
15658
+ zIndex: 9999,
15659
+ fontFamily: tokens.typography.fontFamily
15660
+ },
15661
+ // Launcher button
15662
+ launcher: {
15663
+ width: "56px",
15664
+ height: "56px",
15665
+ borderRadius: tokens.radius.full,
15666
+ background: `linear-gradient(135deg, ${tokens.colors.accentPrimary} 0%, #2d7dd2 100%)`,
15667
+ border: "none",
15668
+ cursor: "pointer",
15669
+ display: "flex",
15670
+ alignItems: "center",
15671
+ justifyContent: "center",
15672
+ boxShadow: `0 4px 20px rgba(76, 154, 255, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3)`,
15673
+ transition: `all ${tokens.transitions.normal}`,
15674
+ color: tokens.colors.textInverse
15675
+ },
15676
+ launcherHover: {
15677
+ transform: "scale(1.05)",
15678
+ boxShadow: `0 6px 24px rgba(76, 154, 255, 0.5), 0 4px 12px rgba(0, 0, 0, 0.4)`
15679
+ },
15680
+ // Badge
15681
+ badge: {
15682
+ position: "absolute",
15683
+ top: "-4px",
15684
+ right: "-4px",
15685
+ minWidth: "20px",
15686
+ height: "20px",
15687
+ borderRadius: tokens.radius.pill,
15688
+ background: tokens.colors.accentError,
15689
+ color: tokens.colors.textInverse,
15690
+ fontSize: tokens.typography.fontSizeXs,
15691
+ fontWeight: tokens.typography.fontWeightSemibold,
15692
+ display: "flex",
15693
+ alignItems: "center",
15694
+ justifyContent: "center",
15695
+ padding: "0 6px",
15696
+ border: `2px solid ${tokens.colors.bgPrimary}`
15697
+ },
15698
+ // Chat panel
15699
+ panel: {
15700
+ position: "absolute",
15701
+ borderRadius: tokens.radius.xl,
15702
+ background: tokens.colors.bgSecondary,
15703
+ border: `1px solid ${tokens.colors.borderPrimary}`,
15704
+ boxShadow: `0 16px 48px rgba(0, 0, 0, 0.5)`,
15705
+ overflow: "hidden",
15706
+ display: "flex",
15707
+ flexDirection: "column",
15708
+ transition: `all ${tokens.transitions.slow}`
15709
+ },
15710
+ panelHidden: {
15711
+ opacity: 0,
15712
+ transform: "scale(0.95) translateY(10px)",
15713
+ pointerEvents: "none"
15714
+ },
15715
+ panelVisible: {
15716
+ opacity: 1,
15717
+ transform: "scale(1) translateY(0)"
15718
+ },
15719
+ // Full screen mode overrides
15720
+ panelFull: {
15721
+ position: "fixed",
15722
+ top: 0,
15723
+ left: 0,
15724
+ right: 0,
15725
+ bottom: 0,
15726
+ borderRadius: 0,
15727
+ width: "100vw",
15728
+ height: "100vh"
15729
+ },
15730
+ // Close button in panel header
15731
+ closeBtn: {
15732
+ width: "32px",
15733
+ height: "32px",
15734
+ borderRadius: tokens.radius.md,
15735
+ background: "transparent",
15736
+ border: "none",
15737
+ color: tokens.colors.textTertiary,
15738
+ cursor: "pointer",
15739
+ display: "flex",
15740
+ alignItems: "center",
15741
+ justifyContent: "center",
15742
+ transition: `all ${tokens.transitions.fast}`
15743
+ },
15744
+ closeBtnHover: {
15745
+ background: tokens.colors.bgTertiary,
15746
+ color: tokens.colors.textPrimary
15747
+ }
15748
+ };
15749
+ function ChatIcon() {
15750
+ return /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
15751
+ }
15752
+ function CloseIcon() {
15753
+ return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15754
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
15755
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
15756
+ ] });
15757
+ }
15758
+ function MinimizeIcon() {
15759
+ return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
15760
+ /* @__PURE__ */ jsx("polyline", { points: "4 14 10 14 10 20" }),
15761
+ /* @__PURE__ */ jsx("polyline", { points: "20 10 14 10 14 4" }),
15762
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
15763
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
15764
+ ] });
15765
+ }
15766
+ function HustleChatWidget({
15767
+ config = {},
15768
+ ...chatProps
15769
+ }) {
15770
+ const {
15771
+ position = "bottom-right",
15772
+ size = "md",
15773
+ title = "Chat",
15774
+ defaultOpen = false,
15775
+ launcherIcon,
15776
+ offset = { x: 24, y: 24 },
15777
+ zIndex = 9999,
15778
+ showBadge = false,
15779
+ badgeContent,
15780
+ launcherStyle,
15781
+ panelStyle,
15782
+ onOpen,
15783
+ onClose,
15784
+ storageKey = "hustle-widget-open"
15785
+ } = config;
15786
+ const [isOpen, setIsOpen] = useState(false);
15787
+ const [isHovered, setIsHovered] = useState(false);
15788
+ const [closeHovered, setCloseHovered] = useState(false);
15789
+ const [mounted, setMounted] = useState(false);
15790
+ useEffect(() => {
15791
+ setMounted(true);
15792
+ if (storageKey && typeof window !== "undefined") {
15793
+ const stored = localStorage.getItem(storageKey);
15794
+ if (stored !== null) {
15795
+ setIsOpen(stored === "true");
15796
+ } else {
15797
+ setIsOpen(defaultOpen);
15798
+ }
15799
+ } else {
15800
+ setIsOpen(defaultOpen);
15801
+ }
15802
+ }, [defaultOpen, storageKey]);
15803
+ useEffect(() => {
15804
+ if (!mounted) return;
15805
+ if (storageKey && typeof window !== "undefined") {
15806
+ localStorage.setItem(storageKey, String(isOpen));
15807
+ }
15808
+ }, [isOpen, storageKey, mounted]);
15809
+ const toggle = useCallback(() => {
15810
+ setIsOpen((prev) => {
15811
+ const next = !prev;
15812
+ if (next) {
15813
+ onOpen?.();
15814
+ } else {
15815
+ onClose?.();
15816
+ }
15817
+ return next;
15818
+ });
15819
+ }, [onOpen, onClose]);
15820
+ const positionStyles = getPositionStyles(position, offset);
15821
+ const sizeConfig = sizeConfigs[size];
15822
+ if (!mounted) {
15823
+ return null;
15824
+ }
15825
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
15826
+ /* @__PURE__ */ jsx("style", { children: animations }),
15827
+ /* @__PURE__ */ jsx("style", { children: `
15828
+ @keyframes hustle-widget-bounce {
15829
+ 0%, 100% { transform: translateY(0); }
15830
+ 50% { transform: translateY(-4px); }
15831
+ }
15832
+ ` }),
15833
+ /* @__PURE__ */ jsxs(
15834
+ "div",
15835
+ {
15836
+ style: {
15837
+ ...widgetStyles.container,
15838
+ ...positionStyles.container,
15839
+ zIndex
15840
+ },
15841
+ children: [
15842
+ /* @__PURE__ */ jsxs(
15843
+ "div",
15844
+ {
15845
+ style: {
15846
+ ...widgetStyles.panel,
15847
+ ...positionStyles.panel,
15848
+ ...size === "full" ? widgetStyles.panelFull : {
15849
+ width: sizeConfig.width,
15850
+ height: sizeConfig.height
15851
+ },
15852
+ ...isOpen ? widgetStyles.panelVisible : widgetStyles.panelHidden,
15853
+ ...panelStyle
15854
+ },
15855
+ children: [
15856
+ /* @__PURE__ */ jsxs(
15857
+ "div",
15858
+ {
15859
+ style: {
15860
+ display: "flex",
15861
+ alignItems: "center",
15862
+ justifyContent: "space-between",
15863
+ padding: `${tokens.spacing.md} ${tokens.spacing.lg}`,
15864
+ background: tokens.colors.bgPrimary,
15865
+ borderBottom: `1px solid ${tokens.colors.borderPrimary}`,
15866
+ borderRadius: `${tokens.radius.xl} ${tokens.radius.xl} 0 0`,
15867
+ flexShrink: 0
15868
+ },
15869
+ children: [
15870
+ /* @__PURE__ */ jsx(
15871
+ "span",
15872
+ {
15873
+ style: {
15874
+ fontWeight: tokens.typography.fontWeightSemibold,
15875
+ color: tokens.colors.textPrimary,
15876
+ fontSize: tokens.typography.fontSizeMd
15877
+ },
15878
+ children: title
15879
+ }
15880
+ ),
15881
+ /* @__PURE__ */ jsx(
15882
+ "button",
15883
+ {
15884
+ onClick: toggle,
15885
+ onMouseEnter: () => setCloseHovered(true),
15886
+ onMouseLeave: () => setCloseHovered(false),
15887
+ style: {
15888
+ ...widgetStyles.closeBtn,
15889
+ ...closeHovered ? widgetStyles.closeBtnHover : {}
15890
+ },
15891
+ title: "Close chat",
15892
+ children: size === "full" ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(CloseIcon, {})
15893
+ }
15894
+ )
15895
+ ]
15896
+ }
15897
+ ),
15898
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: /* @__PURE__ */ jsx(HustleChatInner, { ...chatProps }) })
15899
+ ]
15900
+ }
15901
+ ),
15902
+ (size !== "full" || !isOpen) && /* @__PURE__ */ jsxs(
15903
+ "button",
15904
+ {
15905
+ onClick: toggle,
15906
+ onMouseEnter: () => setIsHovered(true),
15907
+ onMouseLeave: () => setIsHovered(false),
15908
+ style: {
15909
+ ...widgetStyles.launcher,
15910
+ ...isHovered ? widgetStyles.launcherHover : {},
15911
+ ...positionStyles.launcher,
15912
+ ...launcherStyle,
15913
+ ...isOpen ? { opacity: 0, pointerEvents: "none" } : {}
15914
+ },
15915
+ title: isOpen ? "Close chat" : "Open chat",
15916
+ "aria-label": isOpen ? "Close chat" : "Open chat",
15917
+ children: [
15918
+ launcherIcon || /* @__PURE__ */ jsx(ChatIcon, {}),
15919
+ showBadge && badgeContent && !isOpen && /* @__PURE__ */ jsx("span", { style: widgetStyles.badge, children: badgeContent })
15920
+ ]
15921
+ }
15922
+ )
15923
+ ]
15924
+ }
15925
+ )
15926
+ ] });
15927
+ }
15928
+ function HustleChatInner(props) {
15929
+ return /* @__PURE__ */ jsx(HustleChat, { ...props, hideHeader: true });
15930
+ }
15931
+ function getPositionStyles(position, offset, isOpen, size) {
15932
+ const panelGap = 16;
15933
+ switch (position) {
15934
+ case "bottom-right":
15935
+ return {
15936
+ container: {
15937
+ bottom: `${offset.y}px`,
15938
+ right: `${offset.x}px`
15939
+ },
15940
+ launcher: {},
15941
+ panel: {
15942
+ bottom: `${56 + panelGap}px`,
15943
+ right: 0
15944
+ }
15945
+ };
15946
+ case "bottom-left":
15947
+ return {
15948
+ container: {
15949
+ bottom: `${offset.y}px`,
15950
+ left: `${offset.x}px`
15951
+ },
15952
+ launcher: {},
15953
+ panel: {
15954
+ bottom: `${56 + panelGap}px`,
15955
+ left: 0
15956
+ }
15957
+ };
15958
+ case "top-right":
15959
+ return {
15960
+ container: {
15961
+ top: `${offset.y}px`,
15962
+ right: `${offset.x}px`
15963
+ },
15964
+ launcher: {},
15965
+ panel: {
15966
+ top: `${56 + panelGap}px`,
15967
+ right: 0
15968
+ }
15969
+ };
15970
+ case "top-left":
15971
+ return {
15972
+ container: {
15973
+ top: `${offset.y}px`,
15974
+ left: `${offset.x}px`
15975
+ },
15976
+ launcher: {},
15977
+ panel: {
15978
+ top: `${56 + panelGap}px`,
15979
+ left: 0
15980
+ }
15981
+ };
15982
+ }
15983
+ }
15539
15984
 
15540
15985
  // src/utils/index.ts
15541
15986
  function formatFileSize(bytes) {
@@ -15564,6 +16009,6 @@ var DEFAULTS = {
15564
16009
  EMBLEM_MODAL_URL: "https://emblemvault.ai/connect"
15565
16010
  };
15566
16011
 
15567
- export { DEFAULTS, HustleChat, HustleProvider, MarkdownContent, STORAGE_KEYS, availablePlugins, debounce, formatFileSize, getAvailablePlugin, hydratePlugin, migrateFunPlugin, pluginRegistry, predictionMarketPlugin, tokens, useHustle, usePlugins };
16012
+ export { DEFAULTS, HustleChat, HustleChatWidget, HustleProvider, MarkdownContent, STORAGE_KEYS, availablePlugins, debounce, formatFileSize, getAvailablePlugin, hydratePlugin, migrateFunPlugin, pluginRegistry, predictionMarketPlugin, tokens, useHustle, usePlugins };
15568
16013
  //# sourceMappingURL=hustle-react.js.map
15569
16014
  //# sourceMappingURL=hustle-react.js.map