@bytexbyte/nxtlinq-ai-agent-sdk 1.6.29 → 1.6.30

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.
@@ -9,7 +9,11 @@ import { MessageInput } from './MessageInput';
9
9
  import { PresetMessages } from './PresetMessages';
10
10
  import { ModelSelector } from './ModelSelector';
11
11
  import * as walletTextUtils from '../../core/utils/walletTextUtils';
12
+ import { useResizable } from '../../core/lib/useResizable';
13
+ import { useDraggable } from '../../core/lib/useDraggable';
14
+ import useLocalStorage from '../../core/lib/useLocalStorage';
12
15
  import { sdkContainer, floatingButton, chatWindow, chatHeader, headerTitle, headerButton, closeButton, modalOverlay, idvBanner, idvBannerTitle, idvBannerText, idvVerifyButton, idvDismissButton, loadingSpinner, successToast, errorToast, warningToast, infoToast, toastCloseButton } from './styles/isolatedStyles';
16
+ import { resizeHandle } from './styles/isolatedStyles';
13
17
  // Toast Notification Component
14
18
  const ToastNotification = ({ type, message, onClose, isChatOpen = false }) => {
15
19
  const getToastStyles = () => {
@@ -49,7 +53,20 @@ const ToastNotification = ({ type, message, onClose, isChatOpen = false }) => {
49
53
  return (_jsxs("div", { css: getToastStyles(), children: [_jsx("span", { css: css `font-size: 18px !important;`, children: getIcon() }), _jsx("span", { css: css `flex: 1 !important;`, children: message }), _jsx("button", { onClick: onClose, css: toastCloseButton, children: "\u00D7" })] }));
50
54
  };
51
55
  export const ChatBotUI = () => {
52
- const { isOpen, setIsOpen, showPermissionForm, setShowPermissionForm, notification, setNotification, isAITLoading, hitAddress, walletInfo, onVerifyWallet, serviceId, props } = useChatBot();
56
+ const { isOpen, setIsOpen, showPermissionForm, setShowPermissionForm, notification, setNotification, isAITLoading, hitAddress, walletInfo, onVerifyWallet, serviceId, isNeedSignInWithWallet, props } = useChatBot();
57
+ const positionRef = React.useRef({ x: 0, y: 0 });
58
+ const { dimensions, handleResizeStart, positionAdjustment } = useResizable(positionRef);
59
+ const { position, handleDragStart, isDragging, updatePosition } = useDraggable(dimensions);
60
+ // Update position ref when position changes
61
+ React.useEffect(() => {
62
+ positionRef.current = position;
63
+ }, [position]);
64
+ // Update position when resizing to keep bottom-right corner fixed
65
+ React.useEffect(() => {
66
+ if (positionAdjustment) {
67
+ updatePosition(positionAdjustment);
68
+ }
69
+ }, [positionAdjustment, updatePosition]);
53
70
  // IDV suggestion banner state
54
71
  const [showIDVSuggestion, setShowIDVSuggestion] = React.useState(true);
55
72
  const [dismissUntil, setDismissUntil] = React.useState(null);
@@ -57,21 +74,21 @@ export const ChatBotUI = () => {
57
74
  // Check if there's a berifyme token in URL (indicating recent berifyme verification)
58
75
  const urlParams = new URLSearchParams(window.location.search);
59
76
  const hasBerifymeToken = urlParams.get('token') && urlParams.get('method') === 'berifyme';
60
- // Check if wallet is verified with berifyme
61
77
  const isWalletVerifiedWithBerifyme = walletInfo?.id && walletInfo?.method === 'berifyme';
62
78
  // Check if IDV suggestion should be shown
63
79
  React.useEffect(() => {
64
- // Only show suggestion when verification is not required and wallet is not verified with berifyme
65
80
  const shouldShowBanner = hitAddress &&
66
81
  !props.requireWalletIDVVerification &&
67
82
  !hasBerifymeToken &&
68
- !isWalletVerifiedWithBerifyme;
83
+ !isWalletVerifiedWithBerifyme &&
84
+ !isNeedSignInWithWallet;
69
85
  if (shouldShowBanner) {
70
86
  const timer = setTimeout(() => {
71
87
  const shouldShowBannerAfterDelay = hitAddress &&
72
88
  !props.requireWalletIDVVerification &&
73
89
  !hasBerifymeToken &&
74
- !isWalletVerifiedWithBerifyme;
90
+ !isWalletVerifiedWithBerifyme &&
91
+ !isNeedSignInWithWallet;
75
92
  if (shouldShowBannerAfterDelay) {
76
93
  const dismissed = localStorage.getItem('idv-suggestion-dismissed');
77
94
  if (dismissed) {
@@ -95,7 +112,7 @@ export const ChatBotUI = () => {
95
112
  localStorage.removeItem('idv-suggestion-dismissed');
96
113
  clearInterval(countdownTimer);
97
114
  }
98
- }, 60000); // Update every minute
115
+ }, 60000);
99
116
  return () => clearInterval(countdownTimer);
100
117
  }
101
118
  else {
@@ -116,8 +133,7 @@ export const ChatBotUI = () => {
116
133
  setDismissUntil(null);
117
134
  setTimeRemaining('');
118
135
  }
119
- }, 1000); // 1 second delay
120
- // Cleanup timer if component unmounts or dependencies change
136
+ }, 1000);
121
137
  return () => clearTimeout(timer);
122
138
  }
123
139
  else {
@@ -128,15 +144,13 @@ export const ChatBotUI = () => {
128
144
  setDismissUntil(null);
129
145
  setTimeRemaining('');
130
146
  }
131
- }, [hitAddress, walletInfo]);
132
- // Handle dismiss IDV suggestion
147
+ }, [hitAddress, walletInfo, isNeedSignInWithWallet, props.requireWalletIDVVerification]);
133
148
  const handleDismissIDV = () => {
134
- const dismissSeconds = props.idvBannerDismissSeconds || 86400; // Default 24 hours in seconds
135
- const dismissTime = Date.now() + (dismissSeconds * 1000); // Configurable seconds from now
149
+ const dismissSeconds = props.idvBannerDismissSeconds || 86400;
150
+ const dismissTime = Date.now() + (dismissSeconds * 1000);
136
151
  localStorage.setItem('idv-suggestion-dismissed', dismissTime.toString());
137
152
  setShowIDVSuggestion(false);
138
153
  setDismissUntil(dismissTime);
139
- // Show confirmation message
140
154
  const hours = Math.floor(dismissSeconds / 3600);
141
155
  const minutes = Math.floor((dismissSeconds % 3600) / 60);
142
156
  const timeText = hours > 0
@@ -165,9 +179,35 @@ export const ChatBotUI = () => {
165
179
  };
166
180
  }, []);
167
181
  const handleClose = () => {
182
+ // When closing window, update button position to align with window's bottom-right corner
183
+ if (typeof window !== 'undefined') {
184
+ // Window uses left/top, button uses right/bottom
185
+ const windowRight = window.innerWidth - (position.x + dimensions.width);
186
+ const windowBottom = window.innerHeight - (position.y + dimensions.height);
187
+ const newButtonPosition = clampButtonPosition({ right: windowRight, bottom: windowBottom });
188
+ setButtonPosition(newButtonPosition);
189
+ if (hasLoadedButtonPosition.current) {
190
+ setSavedButtonPosition(newButtonPosition);
191
+ }
192
+ }
168
193
  setIsOpen(false);
169
194
  };
170
195
  const handleOpen = () => {
196
+ // Calculate window position based on button position when opening
197
+ // Window's bottom-right corner should align with button's position
198
+ if (typeof window !== 'undefined' && buttonRef.current) {
199
+ const buttonRect = buttonRef.current.getBoundingClientRect();
200
+ // Get button's right and bottom values (distance from viewport edges)
201
+ const buttonRight = window.innerWidth - buttonRect.right;
202
+ const buttonBottom = window.innerHeight - buttonRect.bottom;
203
+ // Window uses left/top, button uses right/bottom
204
+ const windowLeft = window.innerWidth - dimensions.width - buttonRight;
205
+ const windowTop = window.innerHeight - dimensions.height - buttonBottom;
206
+ // Clamp to ensure window stays within viewport (use same margin as button)
207
+ const clampedLeft = Math.max(BUTTON_MARGIN, Math.min(windowLeft, window.innerWidth - dimensions.width - BUTTON_MARGIN));
208
+ const clampedTop = Math.max(BUTTON_MARGIN, Math.min(windowTop, window.innerHeight - dimensions.height - BUTTON_MARGIN));
209
+ updatePosition({ x: clampedLeft, y: clampedTop });
210
+ }
171
211
  setIsOpen(true);
172
212
  };
173
213
  const handleCloseNotification = () => {
@@ -185,11 +225,210 @@ export const ChatBotUI = () => {
185
225
  const handleSettingsClick = () => {
186
226
  setShowPermissionForm(true);
187
227
  };
228
+ // Constants for button positioning
229
+ const BUTTON_MARGIN = 20;
230
+ const BUTTON_MIN_MARGIN = 5;
231
+ const DRAG_THRESHOLD = 5; // Minimum distance in pixels to distinguish drag from click
232
+ // Track window size for responsive button positioning and dragging
233
+ const buttonRef = React.useRef(null);
234
+ const buttonDragState = React.useRef(null);
235
+ const [isButtonDragging, setIsButtonDragging] = React.useState(false);
236
+ const [isButtonDragActive, setIsButtonDragActive] = React.useState(false);
237
+ const hasLoadedButtonPosition = React.useRef(false);
238
+ const buttonDragDistance = React.useRef(0);
239
+ // Track latest button position for saving (to avoid stale closure issues)
240
+ const latestButtonPositionRef = React.useRef({ right: BUTTON_MARGIN, bottom: BUTTON_MARGIN });
241
+ // Load saved position synchronously on initialization to avoid position jump
242
+ const getInitialButtonPosition = () => {
243
+ if (typeof window === 'undefined') {
244
+ return { right: BUTTON_MARGIN, bottom: BUTTON_MARGIN };
245
+ }
246
+ try {
247
+ const saved = localStorage.getItem('ai-agent-button-position');
248
+ if (saved) {
249
+ const parsed = JSON.parse(saved);
250
+ if (parsed && typeof parsed.right === 'number' && typeof parsed.bottom === 'number') {
251
+ return parsed;
252
+ }
253
+ }
254
+ }
255
+ catch (e) {
256
+ // Ignore parse errors, use default
257
+ }
258
+ return { right: BUTTON_MARGIN, bottom: BUTTON_MARGIN };
259
+ };
260
+ const [, setSavedButtonPosition, isButtonPositionInitialized] = useLocalStorage('ai-agent-button-position', null);
261
+ const [buttonPosition, setButtonPosition] = React.useState(getInitialButtonPosition);
262
+ // Keep ref in sync with button position state and mark as loaded when initialized
263
+ React.useEffect(() => {
264
+ latestButtonPositionRef.current = buttonPosition;
265
+ if (isButtonPositionInitialized && !hasLoadedButtonPosition.current) {
266
+ hasLoadedButtonPosition.current = true;
267
+ }
268
+ }, [buttonPosition, isButtonPositionInitialized]);
269
+ // Fast clamp calculation helper (used in both drag and resize)
270
+ const clampButtonPositionFast = React.useCallback((right, bottom, buttonWidth, buttonHeight) => {
271
+ const viewportWidth = window.innerWidth;
272
+ const viewportHeight = window.innerHeight;
273
+ // Clamp horizontally
274
+ const maxRight = viewportWidth - buttonWidth - BUTTON_MARGIN;
275
+ let clampedRight = right;
276
+ if (clampedRight > maxRight) {
277
+ clampedRight = Math.max(BUTTON_MIN_MARGIN, maxRight);
278
+ }
279
+ else if (clampedRight < BUTTON_MARGIN) {
280
+ clampedRight = BUTTON_MARGIN;
281
+ }
282
+ // Clamp vertically
283
+ const maxBottom = viewportHeight - buttonHeight - BUTTON_MARGIN;
284
+ let clampedBottom = bottom;
285
+ if (clampedBottom > maxBottom) {
286
+ clampedBottom = Math.max(BUTTON_MIN_MARGIN, maxBottom);
287
+ }
288
+ else if (clampedBottom < BUTTON_MARGIN) {
289
+ clampedBottom = BUTTON_MARGIN;
290
+ }
291
+ return { right: clampedRight, bottom: clampedBottom };
292
+ }, []);
293
+ // Clamp button position to keep it within viewport
294
+ const clampButtonPosition = React.useCallback((pos, buttonWidth, buttonHeight) => {
295
+ if (typeof window === 'undefined') {
296
+ return pos;
297
+ }
298
+ // Use provided dimensions or get from DOM (avoid getBoundingClientRect during drag)
299
+ let width = buttonWidth;
300
+ let height = buttonHeight;
301
+ if (width === undefined || height === undefined) {
302
+ if (!buttonRef.current) {
303
+ return pos;
304
+ }
305
+ const rect = buttonRef.current.getBoundingClientRect();
306
+ width = rect.width;
307
+ height = rect.height;
308
+ }
309
+ return clampButtonPositionFast(pos.right, pos.bottom, width, height);
310
+ }, [clampButtonPositionFast]);
311
+ const handleButtonDragStart = React.useCallback((event) => {
312
+ // Don't prevent default to allow normal button behavior if it's just a click
313
+ event.stopPropagation();
314
+ if (buttonRef.current) {
315
+ // Cache button dimensions to avoid getBoundingClientRect during drag
316
+ const rect = buttonRef.current.getBoundingClientRect();
317
+ buttonDragState.current = {
318
+ startX: event.clientX,
319
+ startY: event.clientY,
320
+ startRight: buttonPosition.right,
321
+ startBottom: buttonPosition.bottom,
322
+ buttonWidth: rect.width,
323
+ buttonHeight: rect.height
324
+ };
325
+ buttonDragDistance.current = 0;
326
+ setIsButtonDragging(false);
327
+ setIsButtonDragActive(true);
328
+ document.body.style.userSelect = 'none';
329
+ }
330
+ }, [buttonPosition]);
331
+ // Handle button drag move and end
332
+ React.useEffect(() => {
333
+ // Only set up listeners when drag is active
334
+ if (!isButtonDragActive) {
335
+ return;
336
+ }
337
+ const handlePointerMove = (event) => {
338
+ if (!buttonDragState.current)
339
+ return;
340
+ const deltaX = event.clientX - buttonDragState.current.startX;
341
+ const deltaY = event.clientY - buttonDragState.current.startY;
342
+ const dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
343
+ buttonDragDistance.current = dragDistance;
344
+ // Only start dragging if moved more than threshold (to distinguish from click)
345
+ if (dragDistance > DRAG_THRESHOLD && !isButtonDragging) {
346
+ setIsButtonDragging(true);
347
+ document.body.style.cursor = 'grabbing';
348
+ }
349
+ if (dragDistance > DRAG_THRESHOLD) {
350
+ // Calculate new position (right decreases when moving right, bottom decreases when moving down)
351
+ const newRight = buttonDragState.current.startRight - deltaX;
352
+ const newBottom = buttonDragState.current.startBottom - deltaY;
353
+ // Use fast clamp calculation with cached dimensions
354
+ const clampedPos = clampButtonPositionFast(newRight, newBottom, buttonDragState.current.buttonWidth, buttonDragState.current.buttonHeight);
355
+ latestButtonPositionRef.current = clampedPos;
356
+ // Update state immediately (same as window drag) for responsive dragging
357
+ setButtonPosition(clampedPos);
358
+ }
359
+ };
360
+ const handlePointerUp = () => {
361
+ const wasDragging = buttonDragDistance.current > DRAG_THRESHOLD;
362
+ // Save position to localStorage if we were dragging
363
+ if (wasDragging && hasLoadedButtonPosition.current) {
364
+ setSavedButtonPosition(latestButtonPositionRef.current);
365
+ }
366
+ setIsButtonDragging(false);
367
+ setIsButtonDragActive(false);
368
+ buttonDragDistance.current = 0;
369
+ document.body.style.userSelect = '';
370
+ document.body.style.cursor = '';
371
+ buttonDragState.current = null;
372
+ };
373
+ document.addEventListener('pointermove', handlePointerMove);
374
+ document.addEventListener('pointerup', handlePointerUp);
375
+ return () => {
376
+ document.removeEventListener('pointermove', handlePointerMove);
377
+ document.removeEventListener('pointerup', handlePointerUp);
378
+ document.body.style.userSelect = '';
379
+ document.body.style.cursor = '';
380
+ };
381
+ }, [isButtonDragActive, isButtonDragging, setSavedButtonPosition, clampButtonPositionFast]);
382
+ // Update button position on window resize to ensure it stays within viewport
383
+ React.useEffect(() => {
384
+ if (typeof window === 'undefined')
385
+ return;
386
+ const handleResize = () => {
387
+ // Use requestAnimationFrame to ensure DOM is updated
388
+ requestAnimationFrame(() => {
389
+ const clampedPos = clampButtonPosition(buttonPosition);
390
+ if (clampedPos.right !== buttonPosition.right || clampedPos.bottom !== buttonPosition.bottom) {
391
+ setButtonPosition(clampedPos);
392
+ }
393
+ });
394
+ };
395
+ window.addEventListener('resize', handleResize);
396
+ return () => window.removeEventListener('resize', handleResize);
397
+ }, [buttonPosition, clampButtonPosition]);
398
+ // Calculate responsive button style - ensure it overrides base styles
399
+ const getButtonStyle = React.useMemo(() => {
400
+ return css `
401
+ position: fixed !important;
402
+ bottom: ${buttonPosition.bottom}px !important;
403
+ right: ${buttonPosition.right}px !important;
404
+ `;
405
+ }, [buttonPosition]);
188
406
  // Show floating button when chat is closed
189
407
  if (!isOpen) {
190
- return (_jsxs("div", { css: sdkContainer, children: [_jsx("button", { onClick: handleOpen, css: floatingButton, title: "Open AI Agent Chat", children: "AI Agent" }), notification.show && (_jsx(ToastNotification, { type: notification.type, message: notification.message, onClose: handleCloseNotification, isChatOpen: isOpen }))] }));
408
+ return (_jsxs("div", { css: sdkContainer, children: [_jsx("button", { ref: buttonRef, onClick: (e) => {
409
+ // Don't open if we just finished dragging (moved more than threshold)
410
+ if (buttonDragDistance.current > DRAG_THRESHOLD) {
411
+ e.preventDefault();
412
+ e.stopPropagation();
413
+ return;
414
+ }
415
+ handleOpen();
416
+ }, onPointerDown: handleButtonDragStart, css: [floatingButton, getButtonStyle, css `
417
+ cursor: ${isButtonDragging ? 'grabbing' : 'grab'} !important;
418
+ user-select: none !important;
419
+ `], title: "Drag to move or click to open AI Agent Chat", children: "AI Agent" }), notification.show && (_jsx(ToastNotification, { type: notification.type, message: notification.message, onClose: handleCloseNotification, isChatOpen: isOpen }))] }));
191
420
  }
192
- return (_jsxs("div", { css: sdkContainer, children: [_jsxs("div", { css: chatWindow, children: [_jsxs("div", { css: chatHeader, children: [_jsxs("div", { css: css `
421
+ return (_jsxs("div", { css: sdkContainer, children: [_jsxs("div", { css: [chatWindow, css `
422
+ width: ${dimensions.width}px !important;
423
+ height: ${dimensions.height}px !important;
424
+ left: ${position.x}px !important;
425
+ top: ${position.y}px !important;
426
+ bottom: auto !important;
427
+ right: auto !important;
428
+ `], children: [_jsx("div", { css: resizeHandle, onPointerDown: handleResizeStart, title: "Resize", "aria-label": "Resize chat window" }), _jsxs("div", { css: [chatHeader, css `
429
+ cursor: ${isDragging ? 'grabbing' : 'grab'} !important;
430
+ user-select: none !important;
431
+ `], onPointerDown: handleDragStart, title: "Drag to move", children: [_jsxs("div", { css: css `
193
432
  display: flex !important;
194
433
  align-items: center !important;
195
434
  gap: 10px !important;
@@ -203,7 +442,7 @@ export const ChatBotUI = () => {
203
442
  display: flex !important;
204
443
  align-items: center !important;
205
444
  gap: 10px !important;
206
- `, children: [_jsx("button", { onClick: handleSettingsClick, css: headerButton, title: "AIT Settings", children: "\u2699\uFE0F" }), _jsx("button", { onClick: handleClose, css: closeButton, children: "\u00D7" })] })] }), showIDVSuggestion && hitAddress && !props.requireWalletIDVVerification && !hasBerifymeToken && !isWalletVerifiedWithBerifyme && (_jsxs("div", { "data-idv-banner": true, css: idvBanner, children: [_jsx("div", { css: css `
445
+ `, children: [_jsx("button", { onClick: handleSettingsClick, css: headerButton, title: "AIT Settings", onPointerDown: (e) => e.stopPropagation(), children: "\u2699\uFE0F" }), _jsx("button", { onClick: handleClose, css: closeButton, onPointerDown: (e) => e.stopPropagation(), title: "Minimize", children: "\u2212" })] })] }), showIDVSuggestion && hitAddress && !props.requireWalletIDVVerification && !hasBerifymeToken && !isWalletVerifiedWithBerifyme && (_jsxs("div", { "data-idv-banner": true, css: idvBanner, children: [_jsx("div", { css: css `
207
446
  font-size: 20px !important;
208
447
  color: #f39c12 !important;
209
448
  `, children: "\uD83D\uDCA1" }), _jsxs("div", { css: css `flex: 1 !important;`, children: [_jsx("h5", { css: idvBannerTitle, children: walletTextUtils.getWalletText('Recommended: Verify Your Wallet', serviceId) }), _jsx("p", { css: idvBannerText, children: walletTextUtils.getWalletText('While not required, verifying your wallet with Berify.me provides additional security and trust for your AI agent interactions.', serviceId) }), _jsx("button", { onClick: () => onVerifyWallet('berifyme'), css: idvVerifyButton, children: walletTextUtils.getWalletText('Verify Wallet', serviceId) })] }), _jsx("button", { onClick: handleDismissIDV, css: idvDismissButton, title: `Hide for ${Math.floor((props.idvBannerDismissSeconds || 86400) / 3600)} hours`, children: "\u00D7" })] })), !showIDVSuggestion && dismissUntil && timeRemaining && (_jsxs("div", { css: css `
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/ui/MessageList.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAqB/B,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EA4K/B,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/ui/MessageList.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAkC/B,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAsO/B,CAAC"}
@@ -5,7 +5,7 @@ import { css } from '@emotion/react';
5
5
  import { convertUrlsToLinks } from '../../core/utils/urlUtils';
6
6
  import * as walletTextUtils from '../../core/utils/walletTextUtils';
7
7
  import { useChatBot } from '../context/ChatBotContext';
8
- import { messageListContainer, messageBubble, userMessage, messageContent, userMessageContent, retryMessageContent, chatbotButton, connectedButton, loadingIndicator, modelIndicator, modelBadge, modelDot } from './styles/isolatedStyles';
8
+ import { messageListContainer, messageBubble, userMessage, messageContent, userMessageContent, retryMessageContent, chatbotButton, connectedButton, loadingIndicator, modelIndicator, modelBadge, modelDot, streamingCaret, streamingContainer, streamingHeader, streamingIcon, streamingToolName, streamingProgressPercent, streamingProgressContainer, streamingProgressBar, streamingPartialText, streamingStatus, streamingStepsContainer, streamingStepItem, streamingStepCheck } from './styles/isolatedStyles';
9
9
  export const MessageList = () => {
10
10
  const { messages, isLoading, isTtsProcessing, requiresGesture, retryTtsWithGesture, connectWallet, signInWallet, hitAddress, isAutoConnecting, isNeedSignInWithWallet, enableAIT, isAITLoading, isAITEnabling, sendMessage, permissions, availableModels, serviceId } = useChatBot();
11
11
  const messagesEndRef = React.useRef(null);
@@ -16,6 +16,10 @@ export const MessageList = () => {
16
16
  scrollToBottom();
17
17
  }, [messages]);
18
18
  const handleButtonClick = async (buttonType, message) => {
19
+ // Prevent sending messages while AI Agent is processing
20
+ if (isLoading) {
21
+ return;
22
+ }
19
23
  if (buttonType === 'connectWallet') {
20
24
  connectWallet(true);
21
25
  }
@@ -62,7 +66,7 @@ export const MessageList = () => {
62
66
  ? userMessageContent
63
67
  : message.metadata?.isRetry
64
68
  ? retryMessageContent
65
- : messageContent, children: [message.metadata?.isRetry && (_jsx("span", { css: css `margin-right: 8px !important; font-size: 14px !important;`, children: "\uD83D\uDD04" })), message.role === 'assistant' ? convertUrlsToLinks(message.content) : message.content, message.button && (_jsx("div", { css: css `margin-top: 10px !important;`, children: _jsx("button", { onClick: () => {
69
+ : messageContent, children: [message.metadata?.isRetry && (_jsx("span", { css: css `margin-right: 8px !important; font-size: 14px !important;`, children: "\uD83D\uDD04" })), message.isStreaming && message.partialContent && (_jsxs("div", { css: streamingPartialText, children: [message.role === 'assistant' ? convertUrlsToLinks(message.partialContent) : message.partialContent, _jsx("span", { css: streamingCaret, children: "\u258A" })] })), message.isStreaming && !message.partialContent && message.role === 'assistant' && (_jsxs("div", { css: streamingContainer, children: [_jsxs("div", { css: streamingHeader, children: [_jsx("span", { css: streamingIcon, children: "\uD83D\uDD27" }), _jsx("span", { css: streamingToolName, children: message.streamingToolName || 'Processing' }), message.streamingProgress !== undefined && (_jsxs("span", { css: streamingProgressPercent, children: [message.streamingProgress, "%"] }))] }), message.streamingProgress !== undefined && (_jsx("div", { css: streamingProgressContainer, children: _jsx("div", { css: [streamingProgressBar, css `width: ${message.streamingProgress}% !important;`] }) })), message.streamingStatus && (_jsx("div", { css: streamingStatus, children: message.streamingStatus })), message.streamingSteps && message.streamingSteps.length > 0 && (_jsx("div", { css: streamingStepsContainer, children: message.streamingSteps.map((step, idx) => (_jsxs("div", { css: streamingStepItem, children: [_jsx("span", { css: streamingStepCheck, children: "\u2713" }), _jsx("span", { children: step })] }, idx))) }))] })), !message.isStreaming && (message.role === 'assistant' ? convertUrlsToLinks(message.content) : message.content), message.button && (_jsx("div", { css: css `margin-top: 10px !important;`, children: _jsx("button", { onClick: () => {
66
70
  if (message.button && message.button.trim()) {
67
71
  handleButtonClick(message.button, message);
68
72
  }
@@ -2,6 +2,7 @@ export declare const sdkReset: import("@emotion/utils").SerializedStyles;
2
2
  export declare const sdkContainer: import("@emotion/utils").SerializedStyles;
3
3
  export declare const floatingButton: import("@emotion/utils").SerializedStyles;
4
4
  export declare const chatWindow: import("@emotion/utils").SerializedStyles;
5
+ export declare const resizeHandle: import("@emotion/utils").SerializedStyles;
5
6
  export declare const chatHeader: import("@emotion/utils").SerializedStyles;
6
7
  export declare const headerTitle: import("@emotion/utils").SerializedStyles;
7
8
  export declare const headerButton: import("@emotion/utils").SerializedStyles;
@@ -32,4 +33,17 @@ export declare const idvBannerText: import("@emotion/utils").SerializedStyles;
32
33
  export declare const idvVerifyButton: import("@emotion/utils").SerializedStyles;
33
34
  export declare const idvDismissButton: import("@emotion/utils").SerializedStyles;
34
35
  export declare const loadingSpinner: import("@emotion/utils").SerializedStyles;
36
+ export declare const streamingPartialText: import("@emotion/utils").SerializedStyles;
37
+ export declare const streamingContainer: import("@emotion/utils").SerializedStyles;
38
+ export declare const streamingHeader: import("@emotion/utils").SerializedStyles;
39
+ export declare const streamingIcon: import("@emotion/utils").SerializedStyles;
40
+ export declare const streamingToolName: import("@emotion/utils").SerializedStyles;
41
+ export declare const streamingProgressPercent: import("@emotion/utils").SerializedStyles;
42
+ export declare const streamingProgressContainer: import("@emotion/utils").SerializedStyles;
43
+ export declare const streamingProgressBar: import("@emotion/utils").SerializedStyles;
44
+ export declare const streamingStatus: import("@emotion/utils").SerializedStyles;
45
+ export declare const streamingCaret: import("@emotion/utils").SerializedStyles;
46
+ export declare const streamingStepsContainer: import("@emotion/utils").SerializedStyles;
47
+ export declare const streamingStepItem: import("@emotion/utils").SerializedStyles;
48
+ export declare const streamingStepCheck: import("@emotion/utils").SerializedStyles;
35
49
  //# sourceMappingURL=isolatedStyles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"isolatedStyles.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/styles/isolatedStyles.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,2CAwBpB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAmExB,CAAC;AAGF,eAAO,MAAM,cAAc,2CA6C1B,CAAC;AAGF,eAAO,MAAM,UAAU,2CAuCtB,CAAC;AAGF,eAAO,MAAM,UAAU,2CAStB,CAAC;AAGF,eAAO,MAAM,WAAW,2CAMvB,CAAC;AAGF,eAAO,MAAM,YAAY,2CA0BxB,CAAC;AAGF,eAAO,MAAM,WAAW,2CAoBvB,CAAC;AAGF,eAAO,MAAM,oBAAoB,2CAQhC,CAAC;AAGF,eAAO,MAAM,aAAa,2CAKzB,CAAC;AAGF,eAAO,MAAM,WAAW,2CAKvB,CAAC;AAGF,eAAO,MAAM,cAAc,2CAQ1B,CAAC;AAGF,eAAO,MAAM,kBAAkB,2CAQ9B,CAAC;AAGF,eAAO,MAAM,mBAAmB,2CAS/B,CAAC;AAGF,eAAO,MAAM,aAAa,2CAezB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAexB,CAAC;AAGF,eAAO,MAAM,eAAe,2CAc3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,2CAQ5B,CAAC;AAGF,eAAO,MAAM,cAAc,2CAM1B,CAAC;AAGF,eAAO,MAAM,UAAU,2CAYtB,CAAC;AAGF,eAAO,MAAM,QAAQ,2CAMpB,CAAC;AAGF,eAAO,MAAM,iBAAiB,2CAc7B,CAAC;AAGF,eAAO,MAAM,YAAY,2CAIxB,CAAC;AAGF,eAAO,MAAM,UAAU,2CAItB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAIxB,CAAC;AAGF,eAAO,MAAM,SAAS,2CAIrB,CAAC;AAGF,eAAO,MAAM,gBAAgB,2CAiB5B,CAAC;AAGF,eAAO,MAAM,YAAY,2CAyBxB,CAAC;AAGF,eAAO,MAAM,SAAS,2CAWrB,CAAC;AAGF,eAAO,MAAM,cAAc,2CAM1B,CAAC;AAGF,eAAO,MAAM,aAAa,2CAMzB,CAAC;AAGF,eAAO,MAAM,eAAe,2CAoB3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,2CAmB5B,CAAC;AAGF,eAAO,MAAM,cAAc,2CAO1B,CAAC"}
1
+ {"version":3,"file":"isolatedStyles.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/styles/isolatedStyles.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,2CAwBpB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAmExB,CAAC;AAGF,eAAO,MAAM,cAAc,2CAmD1B,CAAC;AAGF,eAAO,MAAM,UAAU,2CAuCtB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAcxB,CAAC;AAGF,eAAO,MAAM,UAAU,2CAStB,CAAC;AAGF,eAAO,MAAM,WAAW,2CAMvB,CAAC;AAGF,eAAO,MAAM,YAAY,2CA0BxB,CAAC;AAGF,eAAO,MAAM,WAAW,2CAoBvB,CAAC;AAGF,eAAO,MAAM,oBAAoB,2CAQhC,CAAC;AAGF,eAAO,MAAM,aAAa,2CAKzB,CAAC;AAGF,eAAO,MAAM,WAAW,2CAKvB,CAAC;AAGF,eAAO,MAAM,cAAc,2CAQ1B,CAAC;AAGF,eAAO,MAAM,kBAAkB,2CAQ9B,CAAC;AAGF,eAAO,MAAM,mBAAmB,2CAS/B,CAAC;AAGF,eAAO,MAAM,aAAa,2CAezB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAexB,CAAC;AAGF,eAAO,MAAM,eAAe,2CAc3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,2CAQ5B,CAAC;AAGF,eAAO,MAAM,cAAc,2CAM1B,CAAC;AAGF,eAAO,MAAM,UAAU,2CAYtB,CAAC;AAGF,eAAO,MAAM,QAAQ,2CAMpB,CAAC;AAGF,eAAO,MAAM,iBAAiB,2CAc7B,CAAC;AAGF,eAAO,MAAM,YAAY,2CAIxB,CAAC;AAGF,eAAO,MAAM,UAAU,2CAItB,CAAC;AAGF,eAAO,MAAM,YAAY,2CAIxB,CAAC;AAGF,eAAO,MAAM,SAAS,2CAIrB,CAAC;AAGF,eAAO,MAAM,gBAAgB,2CAiB5B,CAAC;AAGF,eAAO,MAAM,YAAY,2CAyBxB,CAAC;AAGF,eAAO,MAAM,SAAS,2CAWrB,CAAC;AAGF,eAAO,MAAM,cAAc,2CAM1B,CAAC;AAGF,eAAO,MAAM,aAAa,2CAMzB,CAAC;AAGF,eAAO,MAAM,eAAe,2CAoB3B,CAAC;AAGF,eAAO,MAAM,gBAAgB,2CAmB5B,CAAC;AAGF,eAAO,MAAM,cAAc,2CAO1B,CAAC;AAGF,eAAO,MAAM,oBAAoB,2CAIhC,CAAC;AAGF,eAAO,MAAM,kBAAkB,2CAO9B,CAAC;AAGF,eAAO,MAAM,eAAe,2CAM3B,CAAC;AAGF,eAAO,MAAM,aAAa,2CAczB,CAAC;AAGF,eAAO,MAAM,iBAAiB,2CAK7B,CAAC;AAGF,eAAO,MAAM,wBAAwB,2CAMpC,CAAC;AAGF,eAAO,MAAM,0BAA0B,2CAOtC,CAAC;AAGF,eAAO,MAAM,oBAAoB,2CAIhC,CAAC;AAGF,eAAO,MAAM,eAAe,2CAK3B,CAAC;AAGF,eAAO,MAAM,cAAc,2CAY1B,CAAC;AAGF,eAAO,MAAM,uBAAuB,2CAInC,CAAC;AAGF,eAAO,MAAM,iBAAiB,2CAO7B,CAAC;AAGF,eAAO,MAAM,kBAAkB,2CAE9B,CAAC"}
@@ -97,8 +97,6 @@ export const sdkContainer = css `
97
97
  // Floating button styles
98
98
  export const floatingButton = css `
99
99
  position: fixed !important;
100
- bottom: 20px !important;
101
- right: 20px !important;
102
100
  background-color: #007bff !important;
103
101
  color: white !important;
104
102
  border: none !important;
@@ -112,6 +110,7 @@ export const floatingButton = css `
112
110
  font-weight: 500 !important;
113
111
  font-family: inherit !important;
114
112
  max-width: calc(100vw - 40px) !important;
113
+ white-space: nowrap !important;
115
114
 
116
115
  &:disabled {
117
116
  cursor: not-allowed !important;
@@ -123,23 +122,30 @@ export const floatingButton = css `
123
122
  transform: translateY(-2px) !important;
124
123
  }
125
124
 
126
- /* Mobile responsive adjustments */
125
+ /* Responsive adjustments for small windows */
127
126
  @media (max-width: 768px) {
128
- bottom: 15px !important;
129
- right: 15px !important;
130
127
  max-width: calc(100vw - 30px) !important;
131
128
  padding: 8px 16px !important;
132
129
  font-size: 13px !important;
133
130
  }
134
131
 
135
132
  @media (max-width: 480px) {
136
- bottom: 10px !important;
137
- right: 10px !important;
138
133
  max-width: calc(100vw - 20px) !important;
139
134
  padding: 6px 12px !important;
140
135
  font-size: 12px !important;
141
136
  border-radius: 16px !important;
142
137
  }
138
+
139
+ /* Ensure button is visible when viewport is very small */
140
+ @media (max-height: 200px) {
141
+ padding: 4px 8px !important;
142
+ font-size: 11px !important;
143
+ }
144
+
145
+ @media (max-width: 200px) {
146
+ padding: 4px 8px !important;
147
+ font-size: 11px !important;
148
+ }
143
149
  `;
144
150
  // Chat window container styles
145
151
  export const chatWindow = css `
@@ -182,6 +188,22 @@ export const chatWindow = css `
182
188
  border-radius: 6px !important;
183
189
  }
184
190
  `;
191
+ // Resize handle for chat window (top-left, invisible until hover)
192
+ export const resizeHandle = css `
193
+ position: absolute !important;
194
+ top: 6px !important;
195
+ left: 6px !important;
196
+ width: 18px !important;
197
+ height: 18px !important;
198
+ border-radius: 4px !important;
199
+ background: transparent !important;
200
+ border: none !important;
201
+ box-shadow: none !important;
202
+ cursor: nwse-resize !important;
203
+ z-index: 10 !important;
204
+ display: grid !important;
205
+ place-items: center !important;
206
+ `;
185
207
  // Header styles
186
208
  export const chatHeader = css `
187
209
  padding: 15px !important;
@@ -563,3 +585,112 @@ export const loadingSpinner = css `
563
585
  border-radius: 50% !important;
564
586
  animation: spin 1s linear infinite !important;
565
587
  `;
588
+ // Streaming partial text block (token-level)
589
+ export const streamingPartialText = css `
590
+ color: #333 !important;
591
+ white-space: pre-wrap !important;
592
+ word-break: break-word !important;
593
+ `;
594
+ // Streaming indicator container styles
595
+ export const streamingContainer = css `
596
+ margin-bottom: 12px !important;
597
+ padding: 12px !important;
598
+ background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%) !important;
599
+ border-radius: 8px !important;
600
+ border-left: 3px solid #667eea !important;
601
+ font-family: inherit !important;
602
+ `;
603
+ // Streaming header styles
604
+ export const streamingHeader = css `
605
+ display: flex !important;
606
+ align-items: center !important;
607
+ gap: 8px !important;
608
+ margin-bottom: 8px !important;
609
+ font-family: inherit !important;
610
+ `;
611
+ // Streaming icon styles
612
+ export const streamingIcon = css `
613
+ font-size: 16px !important;
614
+ animation: pulse 1.5s ease-in-out infinite !important;
615
+
616
+ @keyframes pulse {
617
+ 0%, 100% {
618
+ opacity: 1 !important;
619
+ transform: scale(1) !important;
620
+ }
621
+ 50% {
622
+ opacity: 0.6 !important;
623
+ transform: scale(1.1) !important;
624
+ }
625
+ }
626
+ `;
627
+ // Streaming tool name styles
628
+ export const streamingToolName = css `
629
+ font-weight: 600 !important;
630
+ color: #667eea !important;
631
+ font-size: 14px !important;
632
+ font-family: inherit !important;
633
+ `;
634
+ // Streaming progress percentage styles
635
+ export const streamingProgressPercent = css `
636
+ margin-left: auto !important;
637
+ color: #888 !important;
638
+ font-size: 12px !important;
639
+ font-weight: 600 !important;
640
+ font-family: inherit !important;
641
+ `;
642
+ // Streaming progress bar container styles
643
+ export const streamingProgressContainer = css `
644
+ width: 100% !important;
645
+ height: 4px !important;
646
+ background: rgba(102, 126, 234, 0.1) !important;
647
+ border-radius: 2px !important;
648
+ overflow: hidden !important;
649
+ margin-bottom: 8px !important;
650
+ `;
651
+ // Streaming progress bar fill styles
652
+ export const streamingProgressBar = css `
653
+ height: 100% !important;
654
+ background: linear-gradient(90deg, #667eea, #764ba2) !important;
655
+ transition: width 0.3s ease !important;
656
+ `;
657
+ // Streaming status message styles
658
+ export const streamingStatus = css `
659
+ font-size: 13px !important;
660
+ color: #555 !important;
661
+ font-style: italic !important;
662
+ font-family: inherit !important;
663
+ `;
664
+ // Streaming caret (blinking)
665
+ export const streamingCaret = css `
666
+ margin-left: 2px !important;
667
+ animation: blink 1s infinite !important;
668
+
669
+ @keyframes blink {
670
+ 0%, 50% {
671
+ opacity: 1 !important;
672
+ }
673
+ 51%, 100% {
674
+ opacity: 0 !important;
675
+ }
676
+ }
677
+ `;
678
+ // Streaming steps container styles
679
+ export const streamingStepsContainer = css `
680
+ margin-top: 8px !important;
681
+ font-size: 12px !important;
682
+ font-family: inherit !important;
683
+ `;
684
+ // Streaming step item styles
685
+ export const streamingStepItem = css `
686
+ display: flex !important;
687
+ align-items: center !important;
688
+ gap: 6px !important;
689
+ margin-top: 4px !important;
690
+ color: #666 !important;
691
+ font-family: inherit !important;
692
+ `;
693
+ // Streaming step checkmark styles
694
+ export const streamingStepCheck = css `
695
+ color: #4caf50 !important;
696
+ `;
@@ -1,7 +1,7 @@
1
1
  import { SpeechConfig, SpeechSynthesizer } from 'microsoft-cognitiveservices-speech-sdk';
2
2
  import Cookie from 'universal-cookie';
3
3
  // Hardcoded Azure Speech credentials
4
- const SPEECH_KEY = '8b2cec35ebcd4e6e94da56537c5e910c';
4
+ const SPEECH_KEY = '70ab55f06c454e82bcb5ee0c7a5ea25e';
5
5
  const SPEECH_REGION = 'westcentralus';
6
6
  const cookie = new Cookie();
7
7
  /**
@@ -0,0 +1,15 @@
1
+ interface Position {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export declare const useDraggable: (dimensions: {
6
+ width: number;
7
+ height: number;
8
+ }) => {
9
+ position: Position;
10
+ handleDragStart: (event: React.PointerEvent<HTMLDivElement>) => void;
11
+ isDragging: boolean;
12
+ updatePosition: (newPosition: Position) => void;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=useDraggable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDraggable.d.ts","sourceRoot":"","sources":["../../../src/core/lib/useDraggable.ts"],"names":[],"mappings":"AAGA,UAAU,QAAQ;IAChB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAKD,eAAO,MAAM,YAAY,GAAI,YAAY;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;;6BAuFxC,KAAK,CAAC,YAAY,CAAC,cAAc,CAAC;;kCA8EjB,QAAQ;CAgB1D,CAAC"}