@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/NxtlinqAgentChat.d.ts +26 -0
  2. package/dist/NxtlinqAgentChat.d.ts.map +1 -0
  3. package/dist/NxtlinqAgentChat.js +28 -0
  4. package/dist/components/AgentAssistantShell.d.ts +5 -0
  5. package/dist/components/AgentAssistantShell.d.ts.map +1 -0
  6. package/dist/components/AgentAssistantShell.js +52 -0
  7. package/dist/components/AgentComposer.d.ts +3 -0
  8. package/dist/components/AgentComposer.d.ts.map +1 -0
  9. package/dist/components/AgentComposer.js +60 -0
  10. package/dist/components/AgentMessageList.d.ts +3 -0
  11. package/dist/components/AgentMessageList.d.ts.map +1 -0
  12. package/dist/components/AgentMessageList.js +37 -0
  13. package/dist/components/AgentRemoteAudio.d.ts +4 -0
  14. package/dist/components/AgentRemoteAudio.d.ts.map +1 -0
  15. package/dist/components/AgentRemoteAudio.js +34 -0
  16. package/dist/components/AgentVoiceBar.d.ts +3 -0
  17. package/dist/components/AgentVoiceBar.d.ts.map +1 -0
  18. package/dist/components/AgentVoiceBar.js +91 -0
  19. package/dist/components/PresetMessageChips.d.ts +3 -0
  20. package/dist/components/PresetMessageChips.d.ts.map +1 -0
  21. package/dist/components/PresetMessageChips.js +23 -0
  22. package/dist/context/AgentAssistantContext.d.ts +32 -0
  23. package/dist/context/AgentAssistantContext.d.ts.map +1 -0
  24. package/dist/context/AgentAssistantContext.js +159 -0
  25. package/dist/index.d.ts +16 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +12 -0
  28. package/dist/legacy/assets/images/adiSideItalicDataUri.d.ts +2 -0
  29. package/dist/legacy/assets/images/adiSideItalicDataUri.d.ts.map +1 -0
  30. package/dist/legacy/assets/images/adiSideItalicDataUri.js +1 -0
  31. package/dist/legacy/chatbot/ChatBot.d.ts +5 -0
  32. package/dist/legacy/chatbot/ChatBot.d.ts.map +1 -0
  33. package/dist/legacy/chatbot/ChatBot.js +35 -0
  34. package/dist/legacy/chatbot/context/ChatBotContext.d.ts +5 -0
  35. package/dist/legacy/chatbot/context/ChatBotContext.d.ts.map +1 -0
  36. package/dist/legacy/chatbot/context/ChatBotContext.js +2908 -0
  37. package/dist/legacy/chatbot/types/ChatBotTypes.d.ts +166 -0
  38. package/dist/legacy/chatbot/types/ChatBotTypes.d.ts.map +1 -0
  39. package/dist/legacy/chatbot/types/ChatBotTypes.js +1 -0
  40. package/dist/legacy/chatbot/ui/BerifyMeModal.d.ts +17 -0
  41. package/dist/legacy/chatbot/ui/BerifyMeModal.d.ts.map +1 -0
  42. package/dist/legacy/chatbot/ui/BerifyMeModal.js +110 -0
  43. package/dist/legacy/chatbot/ui/ChatBotUI.d.ts +3 -0
  44. package/dist/legacy/chatbot/ui/ChatBotUI.d.ts.map +1 -0
  45. package/dist/legacy/chatbot/ui/ChatBotUI.js +625 -0
  46. package/dist/legacy/chatbot/ui/MessageInput.d.ts +3 -0
  47. package/dist/legacy/chatbot/ui/MessageInput.d.ts.map +1 -0
  48. package/dist/legacy/chatbot/ui/MessageInput.js +321 -0
  49. package/dist/legacy/chatbot/ui/MessageList.d.ts +4 -0
  50. package/dist/legacy/chatbot/ui/MessageList.d.ts.map +1 -0
  51. package/dist/legacy/chatbot/ui/MessageList.js +455 -0
  52. package/dist/legacy/chatbot/ui/ModelSelector.d.ts +4 -0
  53. package/dist/legacy/chatbot/ui/ModelSelector.d.ts.map +1 -0
  54. package/dist/legacy/chatbot/ui/ModelSelector.js +122 -0
  55. package/dist/legacy/chatbot/ui/NotificationModal.d.ts +15 -0
  56. package/dist/legacy/chatbot/ui/NotificationModal.d.ts.map +1 -0
  57. package/dist/legacy/chatbot/ui/NotificationModal.js +53 -0
  58. package/dist/legacy/chatbot/ui/PermissionForm.d.ts +8 -0
  59. package/dist/legacy/chatbot/ui/PermissionForm.d.ts.map +1 -0
  60. package/dist/legacy/chatbot/ui/PermissionForm.js +465 -0
  61. package/dist/legacy/chatbot/ui/PresetMessages.d.ts +4 -0
  62. package/dist/legacy/chatbot/ui/PresetMessages.d.ts.map +1 -0
  63. package/dist/legacy/chatbot/ui/PresetMessages.js +33 -0
  64. package/dist/legacy/chatbot/ui/VoiceModePanel.d.ts +3 -0
  65. package/dist/legacy/chatbot/ui/VoiceModePanel.d.ts.map +1 -0
  66. package/dist/legacy/chatbot/ui/VoiceModePanel.js +95 -0
  67. package/dist/legacy/chatbot/ui/styles/isolatedStyles.d.ts +73 -0
  68. package/dist/legacy/chatbot/ui/styles/isolatedStyles.d.ts.map +1 -0
  69. package/dist/legacy/chatbot/ui/styles/isolatedStyles.js +985 -0
  70. package/dist/legacy/index.d.ts +14 -0
  71. package/dist/legacy/index.d.ts.map +1 -0
  72. package/dist/legacy/index.js +12 -0
  73. package/dist/theme/defaultTheme.d.ts +3 -0
  74. package/dist/theme/defaultTheme.d.ts.map +1 -0
  75. package/dist/theme/defaultTheme.js +20 -0
  76. package/dist/types.d.ts +62 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +1 -0
  79. package/dist/voice/useVoiceConnectOrchestration.d.ts +21 -0
  80. package/dist/voice/useVoiceConnectOrchestration.d.ts.map +1 -0
  81. package/dist/voice/useVoiceConnectOrchestration.js +86 -0
  82. package/dist/voice/useVoiceMicState.d.ts +15 -0
  83. package/dist/voice/useVoiceMicState.d.ts.map +1 -0
  84. package/dist/voice/useVoiceMicState.js +94 -0
  85. package/dist/voice/useVoiceSilenceCommit.d.ts +10 -0
  86. package/dist/voice/useVoiceSilenceCommit.d.ts.map +1 -0
  87. package/dist/voice/useVoiceSilenceCommit.js +67 -0
  88. package/dist/voice/useVoiceTranscriptMessages.d.ts +16 -0
  89. package/dist/voice/useVoiceTranscriptMessages.d.ts.map +1 -0
  90. package/dist/voice/useVoiceTranscriptMessages.js +129 -0
  91. package/dist/voice/useWsRealtimeAudio.d.ts +18 -0
  92. package/dist/voice/useWsRealtimeAudio.d.ts.map +1 -0
  93. package/dist/voice/useWsRealtimeAudio.js +102 -0
  94. package/dist/voice/voiceMicConstants.d.ts +4 -0
  95. package/dist/voice/voiceMicConstants.d.ts.map +1 -0
  96. package/dist/voice/voiceMicConstants.js +10 -0
  97. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts +23 -0
  98. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts.map +1 -0
  99. package/dist/voice/ws/BrowserWsPcmPlayer.js +137 -0
  100. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts +17 -0
  101. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts.map +1 -0
  102. package/dist/voice/ws/BrowserWsPcmRecorder.js +71 -0
  103. package/dist/voice/ws/float32ToPcm16.d.ts +2 -0
  104. package/dist/voice/ws/float32ToPcm16.d.ts.map +1 -0
  105. package/dist/voice/ws/float32ToPcm16.js +8 -0
  106. package/dist/voice/ws/voiceSilenceConstants.d.ts +5 -0
  107. package/dist/voice/ws/voiceSilenceConstants.d.ts.map +1 -0
  108. package/dist/voice/ws/voiceSilenceConstants.js +4 -0
  109. package/dist/voice/ws/wsRealtimeConstants.d.ts +2 -0
  110. package/dist/voice/ws/wsRealtimeConstants.d.ts.map +1 -0
  111. package/dist/voice/ws/wsRealtimeConstants.js +1 -0
  112. package/package.json +60 -0
  113. package/src/NxtlinqAgentChat.tsx +79 -0
  114. package/src/components/AgentAssistantShell.tsx +104 -0
  115. package/src/components/AgentComposer.tsx +134 -0
  116. package/src/components/AgentMessageList.tsx +78 -0
  117. package/src/components/AgentRemoteAudio.tsx +34 -0
  118. package/src/components/AgentVoiceBar.tsx +173 -0
  119. package/src/components/PresetMessageChips.tsx +41 -0
  120. package/src/context/AgentAssistantContext.tsx +276 -0
  121. package/src/index.ts +78 -0
  122. package/src/legacy/assets/images/adiSideItalicDataUri.ts +1 -0
  123. package/src/legacy/chatbot/ChatBot.tsx +61 -0
  124. package/src/legacy/chatbot/context/ChatBotContext.tsx +3227 -0
  125. package/src/legacy/chatbot/types/ChatBotTypes.ts +195 -0
  126. package/src/legacy/chatbot/ui/BerifyMeModal.tsx +145 -0
  127. package/src/legacy/chatbot/ui/ChatBotUI.tsx +949 -0
  128. package/src/legacy/chatbot/ui/MessageInput.tsx +517 -0
  129. package/src/legacy/chatbot/ui/MessageList.tsx +764 -0
  130. package/src/legacy/chatbot/ui/ModelSelector.tsx +190 -0
  131. package/src/legacy/chatbot/ui/NotificationModal.tsx +110 -0
  132. package/src/legacy/chatbot/ui/PermissionForm.tsx +632 -0
  133. package/src/legacy/chatbot/ui/PresetMessages.tsx +50 -0
  134. package/src/legacy/chatbot/ui/VoiceModePanel.tsx +168 -0
  135. package/src/legacy/chatbot/ui/styles/isolatedStyles.ts +1058 -0
  136. package/src/legacy/index.ts +26 -0
  137. package/src/theme/defaultTheme.ts +22 -0
  138. package/src/types.ts +65 -0
  139. package/src/voice/useVoiceConnectOrchestration.ts +117 -0
  140. package/src/voice/useVoiceMicState.ts +117 -0
  141. package/src/voice/useVoiceTranscriptMessages.ts +173 -0
  142. package/src/voice/voiceMicConstants.ts +13 -0
@@ -0,0 +1,625 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import { css } from '@emotion/react';
4
+ import * as React from 'react';
5
+ import { useDraggable, useLocalStorage, useResizable, walletTextUtils } from '@bytexbyte/nxtlinq-ai-agent-web-development';
6
+ import { useChatBot } from '../context/ChatBotContext';
7
+ import GraphicEqIcon from '@mui/icons-material/GraphicEq';
8
+ import { MessageInput } from './MessageInput';
9
+ import { MessageList } from './MessageList';
10
+ import { ModelSelector } from './ModelSelector';
11
+ import { PermissionForm } from './PermissionForm';
12
+ import { PresetMessages } from './PresetMessages';
13
+ import { VoiceModePanel } from './VoiceModePanel';
14
+ import { chatHeader, chatWindow, closeButton, errorToast, floatingButton, headerButton, headerTitle, idvBanner, idvBannerText, idvBannerTitle, idvDismissButton, idvVerifyButton, infoToast, loadingSpinner, modalOverlay, resizeHandleNE, resizeHandleNW, resizeHandleSE, resizeHandleSW, sdkContainer, successToast, toastCloseButton, warningToast } from './styles/isolatedStyles';
15
+ /** Header drag band inset so it does not overlap corner resize hit areas */
16
+ const DRAG_CORNER_EXCLUSION_PX = 20;
17
+ const MOBILE_BREAKPOINT = 768;
18
+ const MOBILE_EDGE_MARGIN = 12;
19
+ const MOBILE_FAB_POSITION = { right: MOBILE_EDGE_MARGIN, bottom: MOBILE_EDGE_MARGIN };
20
+ const isMobileViewport = () => typeof window !== 'undefined' && window.innerWidth <= MOBILE_BREAKPOINT;
21
+ // Toast Notification Component
22
+ const ToastNotification = ({ type, message, onClose, isChatOpen = false }) => {
23
+ const getToastStyles = () => {
24
+ const baseStyles = css `
25
+ ${isChatOpen ? css `
26
+ top: 20px !important;
27
+ right: 20px !important;
28
+ max-width: 480px !important;
29
+ ` : css `
30
+ bottom: 20px !important;
31
+ right: 20px !important;
32
+ `}
33
+ `;
34
+ switch (type) {
35
+ case 'success':
36
+ return css `${successToast} ${baseStyles}`;
37
+ case 'error':
38
+ return css `${errorToast} ${baseStyles}`;
39
+ case 'warning':
40
+ return css `${warningToast} ${baseStyles}`;
41
+ default:
42
+ return css `${infoToast} ${baseStyles}`;
43
+ }
44
+ };
45
+ const getIcon = () => {
46
+ switch (type) {
47
+ case 'success':
48
+ return '✅';
49
+ case 'error':
50
+ return '❌';
51
+ case 'warning':
52
+ return '⚠️';
53
+ default:
54
+ return 'ℹ️';
55
+ }
56
+ };
57
+ 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" })] }));
58
+ };
59
+ export const ChatBotUI = () => {
60
+ const { isOpen, setIsOpen, showPermissionForm, setShowPermissionForm, notification, setNotification, isAITLoading, hitAddress, walletInfo, onVerifyWallet, serviceId, isNeedSignInWithWallet, piiDisplayMode, isVoiceMode, isVoiceConnecting, enterVoiceMode, exitVoiceMode, props } = useChatBot();
61
+ const positionRef = React.useRef({ x: 0, y: 0 });
62
+ const { dimensions, handleResizeStart, positionAdjustment } = useResizable(positionRef);
63
+ const { position, handleDragStart, isDragging, updatePosition } = useDraggable(dimensions);
64
+ const [mobileLayout, setMobileLayout] = React.useState(isMobileViewport);
65
+ React.useEffect(() => {
66
+ if (typeof window === 'undefined')
67
+ return;
68
+ const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`);
69
+ const sync = () => setMobileLayout(mq.matches);
70
+ sync();
71
+ mq.addEventListener('change', sync);
72
+ return () => mq.removeEventListener('change', sync);
73
+ }, []);
74
+ // Update position ref when position changes
75
+ React.useEffect(() => {
76
+ positionRef.current = position;
77
+ }, [position]);
78
+ // Update position when resizing to keep bottom-right corner fixed
79
+ React.useEffect(() => {
80
+ if (positionAdjustment) {
81
+ updatePosition(positionAdjustment);
82
+ }
83
+ }, [positionAdjustment, updatePosition]);
84
+ // IDV suggestion banner state
85
+ const [showIDVSuggestion, setShowIDVSuggestion] = React.useState(true);
86
+ const [dismissUntil, setDismissUntil] = React.useState(null);
87
+ const [timeRemaining, setTimeRemaining] = React.useState('');
88
+ const countdownIntervalRef = React.useRef(null);
89
+ // Check if there's a berifyme token in URL (indicating recent berifyme verification)
90
+ const urlParams = new URLSearchParams(window.location.search);
91
+ const hasBerifymeToken = urlParams.get('token') && urlParams.get('method') === 'berifyme';
92
+ const isWalletVerifiedWithBerifyme = walletInfo?.id && walletInfo?.method === 'berifyme';
93
+ // Helper function to update IDV suggestion state based on storage value
94
+ const updateIDVSuggestionState = React.useCallback((dismissedValue) => {
95
+ // Clear any existing countdown interval
96
+ if (countdownIntervalRef.current) {
97
+ clearInterval(countdownIntervalRef.current);
98
+ countdownIntervalRef.current = null;
99
+ }
100
+ if (dismissedValue) {
101
+ const dismissTime = parseInt(dismissedValue);
102
+ const now = Date.now();
103
+ const timeLeft = dismissTime - now;
104
+ if (timeLeft > 0) {
105
+ setShowIDVSuggestion(false);
106
+ setDismissUntil(dismissTime);
107
+ // Calculate initial time remaining immediately
108
+ const hours = Math.floor(timeLeft / (1000 * 60 * 60));
109
+ const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
110
+ setTimeRemaining(`${hours}h ${minutes}m`);
111
+ // Set up countdown interval to update time remaining every minute
112
+ countdownIntervalRef.current = setInterval(() => {
113
+ const remaining = dismissTime - Date.now();
114
+ if (remaining > 0) {
115
+ const hours = Math.floor(remaining / (1000 * 60 * 60));
116
+ const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
117
+ setTimeRemaining(`${hours}h ${minutes}m`);
118
+ }
119
+ else {
120
+ setShowIDVSuggestion(true);
121
+ setDismissUntil(null);
122
+ setTimeRemaining('');
123
+ localStorage.removeItem('idv-suggestion-dismissed');
124
+ if (countdownIntervalRef.current) {
125
+ clearInterval(countdownIntervalRef.current);
126
+ countdownIntervalRef.current = null;
127
+ }
128
+ }
129
+ }, 60000);
130
+ }
131
+ else {
132
+ localStorage.removeItem('idv-suggestion-dismissed');
133
+ setShowIDVSuggestion(true);
134
+ setDismissUntil(null);
135
+ setTimeRemaining('');
136
+ }
137
+ }
138
+ else {
139
+ setShowIDVSuggestion(true);
140
+ setDismissUntil(null);
141
+ setTimeRemaining('');
142
+ }
143
+ }, []);
144
+ // Check if IDV suggestion should be shown
145
+ React.useEffect(() => {
146
+ const shouldShowBanner = hitAddress &&
147
+ !props.requireWalletIDVVerification &&
148
+ !hasBerifymeToken &&
149
+ !isWalletVerifiedWithBerifyme &&
150
+ !isNeedSignInWithWallet;
151
+ if (shouldShowBanner) {
152
+ const timer = setTimeout(() => {
153
+ const shouldShowBannerAfterDelay = hitAddress &&
154
+ !props.requireWalletIDVVerification &&
155
+ !hasBerifymeToken &&
156
+ !isWalletVerifiedWithBerifyme &&
157
+ !isNeedSignInWithWallet;
158
+ if (shouldShowBannerAfterDelay) {
159
+ const dismissed = localStorage.getItem('idv-suggestion-dismissed');
160
+ updateIDVSuggestionState(dismissed);
161
+ }
162
+ else {
163
+ setShowIDVSuggestion(false);
164
+ setDismissUntil(null);
165
+ setTimeRemaining('');
166
+ }
167
+ }, 1000);
168
+ return () => {
169
+ clearTimeout(timer);
170
+ // Clear countdown interval on cleanup
171
+ if (countdownIntervalRef.current) {
172
+ clearInterval(countdownIntervalRef.current);
173
+ countdownIntervalRef.current = null;
174
+ }
175
+ };
176
+ }
177
+ else {
178
+ // Don't show when:
179
+ // - No wallet connected (no hitAddress)
180
+ // - Wallet is already verified (walletInfo?.id exists)
181
+ setShowIDVSuggestion(false);
182
+ setDismissUntil(null);
183
+ setTimeRemaining('');
184
+ }
185
+ }, [hitAddress, walletInfo, isNeedSignInWithWallet, props.requireWalletIDVVerification, updateIDVSuggestionState]);
186
+ const handleDismissIDV = () => {
187
+ const dismissSeconds = props.idvBannerDismissSeconds || 86400;
188
+ const dismissTime = Date.now() + (dismissSeconds * 1000);
189
+ localStorage.setItem('idv-suggestion-dismissed', dismissTime.toString());
190
+ setShowIDVSuggestion(false);
191
+ setDismissUntil(dismissTime);
192
+ // Calculate and set initial time remaining
193
+ const initialHours = Math.floor(dismissSeconds / 3600);
194
+ const initialMinutes = Math.floor((dismissSeconds % 3600) / 60);
195
+ setTimeRemaining(`${initialHours}h ${initialMinutes}m`);
196
+ // Set up countdown interval
197
+ if (countdownIntervalRef.current) {
198
+ clearInterval(countdownIntervalRef.current);
199
+ }
200
+ countdownIntervalRef.current = setInterval(() => {
201
+ const remaining = dismissTime - Date.now();
202
+ if (remaining > 0) {
203
+ const remainingHours = Math.floor(remaining / (1000 * 60 * 60));
204
+ const remainingMinutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
205
+ setTimeRemaining(`${remainingHours}h ${remainingMinutes}m`);
206
+ }
207
+ else {
208
+ setShowIDVSuggestion(true);
209
+ setDismissUntil(null);
210
+ setTimeRemaining('');
211
+ localStorage.removeItem('idv-suggestion-dismissed');
212
+ if (countdownIntervalRef.current) {
213
+ clearInterval(countdownIntervalRef.current);
214
+ countdownIntervalRef.current = null;
215
+ }
216
+ }
217
+ }, 60000);
218
+ const hours = Math.floor(dismissSeconds / 3600);
219
+ const minutes = Math.floor((dismissSeconds % 3600) / 60);
220
+ const timeText = hours > 0
221
+ ? (minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`)
222
+ : `${minutes}m`;
223
+ setNotification({
224
+ show: true,
225
+ type: 'info',
226
+ message: walletTextUtils.getWalletText(`IDV suggestion hidden for ${timeText}. You can still verify your wallet anytime.`, serviceId),
227
+ autoHide: true,
228
+ duration: 3000
229
+ });
230
+ };
231
+ // Add CSS animation for loading spinner
232
+ React.useEffect(() => {
233
+ const style = document.createElement('style');
234
+ style.textContent = `
235
+ @keyframes spin {
236
+ 0% { transform: rotate(0deg); }
237
+ 100% { transform: rotate(360deg); }
238
+ }
239
+ `;
240
+ document.head.appendChild(style);
241
+ return () => {
242
+ document.head.removeChild(style);
243
+ };
244
+ }, []);
245
+ const handleClose = () => {
246
+ if (typeof window !== 'undefined') {
247
+ if (mobileLayout) {
248
+ setButtonPosition(MOBILE_FAB_POSITION);
249
+ latestButtonPositionRef.current = MOBILE_FAB_POSITION;
250
+ }
251
+ else {
252
+ const windowRight = window.innerWidth - (position.x + dimensions.width);
253
+ const windowBottom = window.innerHeight - (position.y + dimensions.height);
254
+ const newButtonPosition = clampButtonPosition({ right: windowRight, bottom: windowBottom });
255
+ setButtonPosition(newButtonPosition);
256
+ if (hasLoadedButtonPosition.current) {
257
+ setSavedButtonPosition(newButtonPosition);
258
+ }
259
+ }
260
+ }
261
+ setIsOpen(false);
262
+ };
263
+ const handleOpen = () => {
264
+ if (typeof window !== 'undefined') {
265
+ if (mobileLayout) {
266
+ updatePosition({ x: MOBILE_EDGE_MARGIN, y: MOBILE_EDGE_MARGIN });
267
+ }
268
+ else if (buttonRef.current) {
269
+ // Window's bottom-right corner aligns with the FAB
270
+ const buttonRect = buttonRef.current.getBoundingClientRect();
271
+ const buttonRight = window.innerWidth - buttonRect.right;
272
+ const buttonBottom = window.innerHeight - buttonRect.bottom;
273
+ const windowLeft = window.innerWidth - dimensions.width - buttonRight;
274
+ const windowTop = window.innerHeight - dimensions.height - buttonBottom;
275
+ const clampedLeft = Math.max(BUTTON_MARGIN, Math.min(windowLeft, window.innerWidth - dimensions.width - BUTTON_MARGIN));
276
+ const clampedTop = Math.max(BUTTON_MARGIN, Math.min(windowTop, window.innerHeight - dimensions.height - BUTTON_MARGIN));
277
+ updatePosition({ x: clampedLeft, y: clampedTop });
278
+ }
279
+ }
280
+ setIsOpen(true);
281
+ };
282
+ const handleCloseNotification = () => {
283
+ setNotification((prev) => ({ ...prev, show: false }));
284
+ };
285
+ // Auto-hide notification when autoHide is true and duration is set
286
+ React.useEffect(() => {
287
+ if (notification.show && notification.autoHide && notification.duration && notification.duration > 0) {
288
+ const timer = setTimeout(() => {
289
+ setNotification((prev) => ({ ...prev, show: false }));
290
+ }, notification.duration);
291
+ return () => clearTimeout(timer);
292
+ }
293
+ }, [notification.show, notification.autoHide, notification.duration, setNotification]);
294
+ const handleSettingsClick = () => {
295
+ setShowPermissionForm(true);
296
+ };
297
+ // Constants for button positioning
298
+ const BUTTON_MARGIN = 20;
299
+ const BUTTON_MIN_MARGIN = 5;
300
+ const DRAG_THRESHOLD = 5; // Minimum distance in pixels to distinguish drag from click
301
+ // Track window size for responsive button positioning and dragging
302
+ const buttonRef = React.useRef(null);
303
+ const buttonDragState = React.useRef(null);
304
+ const [isButtonDragging, setIsButtonDragging] = React.useState(false);
305
+ const [isButtonDragActive, setIsButtonDragActive] = React.useState(false);
306
+ const hasLoadedButtonPosition = React.useRef(false);
307
+ const buttonDragDistance = React.useRef(0);
308
+ // Track latest button position for saving (to avoid stale closure issues)
309
+ const latestButtonPositionRef = React.useRef({ right: BUTTON_MARGIN, bottom: BUTTON_MARGIN });
310
+ // Load saved position synchronously on initialization to avoid position jump
311
+ const getInitialButtonPosition = () => {
312
+ if (typeof window === 'undefined') {
313
+ return { right: BUTTON_MARGIN, bottom: BUTTON_MARGIN };
314
+ }
315
+ if (isMobileViewport()) {
316
+ return { ...MOBILE_FAB_POSITION };
317
+ }
318
+ try {
319
+ const saved = localStorage.getItem('ai-agent-button-position');
320
+ if (saved) {
321
+ const parsed = JSON.parse(saved);
322
+ if (parsed && typeof parsed.right === 'number' && typeof parsed.bottom === 'number') {
323
+ return parsed;
324
+ }
325
+ }
326
+ }
327
+ catch (e) {
328
+ // Ignore parse errors, use default
329
+ }
330
+ return { right: BUTTON_MARGIN, bottom: BUTTON_MARGIN };
331
+ };
332
+ const [, setSavedButtonPosition, isButtonPositionInitialized] = useLocalStorage('ai-agent-button-position', null);
333
+ const [buttonPosition, setButtonPosition] = React.useState(getInitialButtonPosition);
334
+ // Keep ref in sync with button position state and mark as loaded when initialized
335
+ React.useEffect(() => {
336
+ latestButtonPositionRef.current = buttonPosition;
337
+ if (isButtonPositionInitialized && !hasLoadedButtonPosition.current) {
338
+ hasLoadedButtonPosition.current = true;
339
+ }
340
+ }, [buttonPosition, isButtonPositionInitialized]);
341
+ // Fast clamp calculation helper (used in both drag and resize)
342
+ const clampButtonPositionFast = React.useCallback((right, bottom, buttonWidth, buttonHeight) => {
343
+ const viewportWidth = window.innerWidth;
344
+ const viewportHeight = window.innerHeight;
345
+ // Clamp horizontally
346
+ const maxRight = viewportWidth - buttonWidth - BUTTON_MARGIN;
347
+ let clampedRight = right;
348
+ if (clampedRight > maxRight) {
349
+ clampedRight = Math.max(BUTTON_MIN_MARGIN, maxRight);
350
+ }
351
+ else if (clampedRight < BUTTON_MARGIN) {
352
+ clampedRight = BUTTON_MARGIN;
353
+ }
354
+ // Clamp vertically
355
+ const maxBottom = viewportHeight - buttonHeight - BUTTON_MARGIN;
356
+ let clampedBottom = bottom;
357
+ if (clampedBottom > maxBottom) {
358
+ clampedBottom = Math.max(BUTTON_MIN_MARGIN, maxBottom);
359
+ }
360
+ else if (clampedBottom < BUTTON_MARGIN) {
361
+ clampedBottom = BUTTON_MARGIN;
362
+ }
363
+ return { right: clampedRight, bottom: clampedBottom };
364
+ }, []);
365
+ // Clamp button position to keep it within viewport
366
+ const clampButtonPosition = React.useCallback((pos, buttonWidth, buttonHeight) => {
367
+ if (typeof window === 'undefined') {
368
+ return pos;
369
+ }
370
+ // Use provided dimensions or get from DOM (avoid getBoundingClientRect during drag)
371
+ let width = buttonWidth;
372
+ let height = buttonHeight;
373
+ if (width === undefined || height === undefined) {
374
+ if (!buttonRef.current) {
375
+ return pos;
376
+ }
377
+ const rect = buttonRef.current.getBoundingClientRect();
378
+ width = rect.width;
379
+ height = rect.height;
380
+ }
381
+ return clampButtonPositionFast(pos.right, pos.bottom, width, height);
382
+ }, [clampButtonPositionFast]);
383
+ const handleButtonDragStart = React.useCallback((event) => {
384
+ if (mobileLayout) {
385
+ return;
386
+ }
387
+ event.stopPropagation();
388
+ if (buttonRef.current) {
389
+ // Cache button dimensions to avoid getBoundingClientRect during drag
390
+ const rect = buttonRef.current.getBoundingClientRect();
391
+ buttonDragState.current = {
392
+ startX: event.clientX,
393
+ startY: event.clientY,
394
+ startRight: buttonPosition.right,
395
+ startBottom: buttonPosition.bottom,
396
+ buttonWidth: rect.width,
397
+ buttonHeight: rect.height
398
+ };
399
+ buttonDragDistance.current = 0;
400
+ setIsButtonDragging(false);
401
+ setIsButtonDragActive(true);
402
+ document.body.style.userSelect = 'none';
403
+ }
404
+ }, [buttonPosition, mobileLayout]);
405
+ React.useEffect(() => {
406
+ if (!mobileLayout) {
407
+ return;
408
+ }
409
+ setButtonPosition(MOBILE_FAB_POSITION);
410
+ latestButtonPositionRef.current = MOBILE_FAB_POSITION;
411
+ }, [mobileLayout]);
412
+ // Handle button drag move and end
413
+ React.useEffect(() => {
414
+ if (mobileLayout || !isButtonDragActive) {
415
+ return;
416
+ }
417
+ const handlePointerMove = (event) => {
418
+ if (!buttonDragState.current)
419
+ return;
420
+ const deltaX = event.clientX - buttonDragState.current.startX;
421
+ const deltaY = event.clientY - buttonDragState.current.startY;
422
+ const dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
423
+ buttonDragDistance.current = dragDistance;
424
+ // Only start dragging if moved more than threshold (to distinguish from click)
425
+ if (dragDistance > DRAG_THRESHOLD && !isButtonDragging) {
426
+ setIsButtonDragging(true);
427
+ document.body.style.cursor = 'grabbing';
428
+ }
429
+ if (dragDistance > DRAG_THRESHOLD) {
430
+ // Calculate new position (right decreases when moving right, bottom decreases when moving down)
431
+ const newRight = buttonDragState.current.startRight - deltaX;
432
+ const newBottom = buttonDragState.current.startBottom - deltaY;
433
+ // Use fast clamp calculation with cached dimensions
434
+ const clampedPos = clampButtonPositionFast(newRight, newBottom, buttonDragState.current.buttonWidth, buttonDragState.current.buttonHeight);
435
+ latestButtonPositionRef.current = clampedPos;
436
+ // Update state immediately (same as window drag) for responsive dragging
437
+ setButtonPosition(clampedPos);
438
+ }
439
+ };
440
+ const handlePointerUp = () => {
441
+ const wasDragging = buttonDragDistance.current > DRAG_THRESHOLD;
442
+ // Save position to localStorage if we were dragging
443
+ if (wasDragging && hasLoadedButtonPosition.current) {
444
+ setSavedButtonPosition(latestButtonPositionRef.current);
445
+ }
446
+ setIsButtonDragging(false);
447
+ setIsButtonDragActive(false);
448
+ buttonDragDistance.current = 0;
449
+ document.body.style.userSelect = '';
450
+ document.body.style.cursor = '';
451
+ buttonDragState.current = null;
452
+ };
453
+ document.addEventListener('pointermove', handlePointerMove);
454
+ document.addEventListener('pointerup', handlePointerUp);
455
+ return () => {
456
+ document.removeEventListener('pointermove', handlePointerMove);
457
+ document.removeEventListener('pointerup', handlePointerUp);
458
+ document.body.style.userSelect = '';
459
+ document.body.style.cursor = '';
460
+ };
461
+ }, [isButtonDragActive, isButtonDragging, setSavedButtonPosition, clampButtonPositionFast, mobileLayout]);
462
+ // Update button position on window resize to ensure it stays within viewport
463
+ React.useEffect(() => {
464
+ if (typeof window === 'undefined')
465
+ return;
466
+ const handleResize = () => {
467
+ requestAnimationFrame(() => {
468
+ if (mobileLayout) {
469
+ setButtonPosition(MOBILE_FAB_POSITION);
470
+ latestButtonPositionRef.current = MOBILE_FAB_POSITION;
471
+ return;
472
+ }
473
+ const clampedPos = clampButtonPosition(buttonPosition);
474
+ if (clampedPos.right !== buttonPosition.right || clampedPos.bottom !== buttonPosition.bottom) {
475
+ setButtonPosition(clampedPos);
476
+ }
477
+ });
478
+ };
479
+ window.addEventListener('resize', handleResize);
480
+ return () => window.removeEventListener('resize', handleResize);
481
+ }, [buttonPosition, clampButtonPosition, mobileLayout]);
482
+ const getButtonStyle = React.useMemo(() => {
483
+ if (mobileLayout) {
484
+ return css `
485
+ position: fixed !important;
486
+ bottom: calc(${MOBILE_EDGE_MARGIN}px + env(safe-area-inset-bottom, 0px)) !important;
487
+ right: calc(${MOBILE_EDGE_MARGIN}px + env(safe-area-inset-right, 0px)) !important;
488
+ left: auto !important;
489
+ top: auto !important;
490
+ touch-action: manipulation !important;
491
+ `;
492
+ }
493
+ return css `
494
+ position: fixed !important;
495
+ bottom: ${buttonPosition.bottom}px !important;
496
+ right: ${buttonPosition.right}px !important;
497
+ `;
498
+ }, [buttonPosition, mobileLayout]);
499
+ // Show floating button when chat is closed
500
+ if (!isOpen) {
501
+ return (_jsxs("div", { css: sdkContainer, children: [_jsx("button", { ref: buttonRef, onClick: (e) => {
502
+ // Don't open if we just finished dragging (moved more than threshold)
503
+ if (buttonDragDistance.current > DRAG_THRESHOLD) {
504
+ e.preventDefault();
505
+ e.stopPropagation();
506
+ return;
507
+ }
508
+ handleOpen();
509
+ }, onPointerDown: mobileLayout ? undefined : handleButtonDragStart, css: [floatingButton, getButtonStyle, css `
510
+ cursor: ${mobileLayout ? 'pointer' : isButtonDragging ? 'grabbing' : 'grab'} !important;
511
+ user-select: none !important;
512
+ `], title: mobileLayout ? 'Open AI Agent Chat' : '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 }))] }));
513
+ }
514
+ return (_jsxs("div", { css: sdkContainer, children: [_jsxs("div", { css: [chatWindow, css `
515
+ width: ${dimensions.width}px !important;
516
+ height: ${dimensions.height}px !important;
517
+ left: ${position.x}px !important;
518
+ top: ${position.y}px !important;
519
+ bottom: auto !important;
520
+ right: auto !important;
521
+ ${mobileLayout ? css `
522
+ width: calc(100vw - ${MOBILE_EDGE_MARGIN * 2}px) !important;
523
+ height: calc(100dvh - ${MOBILE_EDGE_MARGIN * 2}px) !important;
524
+ max-width: calc(100vw - ${MOBILE_EDGE_MARGIN * 2}px) !important;
525
+ max-height: calc(100dvh - ${MOBILE_EDGE_MARGIN * 2}px) !important;
526
+ left: ${MOBILE_EDGE_MARGIN}px !important;
527
+ top: ${MOBILE_EDGE_MARGIN}px !important;
528
+ ` : ''}
529
+ `], children: [!mobileLayout && (_jsxs(_Fragment, { children: [_jsx("div", { css: resizeHandleNW, onPointerDown: handleResizeStart('nw'), title: "Resize", "aria-label": "Resize chat window from top-left" }), _jsx("div", { css: resizeHandleNE, onPointerDown: handleResizeStart('ne'), title: "Resize", "aria-label": "Resize chat window from top-right" }), _jsx("div", { css: resizeHandleSW, onPointerDown: handleResizeStart('sw'), title: "Resize", "aria-label": "Resize chat window from bottom-left" }), _jsx("div", { css: resizeHandleSE, onPointerDown: handleResizeStart('se'), title: "Resize", "aria-label": "Resize chat window from bottom-right" })] })), _jsxs("div", { css: [chatHeader, css `
530
+ position: relative !important;
531
+ `], children: [_jsx("div", { css: css `
532
+ position: absolute !important;
533
+ left: ${DRAG_CORNER_EXCLUSION_PX}px !important;
534
+ right: ${DRAG_CORNER_EXCLUSION_PX}px !important;
535
+ top: 0 !important;
536
+ bottom: 0 !important;
537
+ z-index: 1 !important;
538
+ cursor: ${isDragging ? 'grabbing' : 'grab'} !important;
539
+ user-select: none !important;
540
+ `, onPointerDown: handleDragStart, title: "Drag to move", "aria-hidden": true }), _jsxs("div", { css: css `
541
+ position: relative !important;
542
+ z-index: 2 !important;
543
+ display: flex !important;
544
+ justify-content: space-between !important;
545
+ align-items: center !important;
546
+ width: 100% !important;
547
+ pointer-events: none !important;
548
+ `, children: [_jsxs("div", { css: css `
549
+ display: flex !important;
550
+ align-items: center !important;
551
+ gap: 10px !important;
552
+ pointer-events: none !important;
553
+ `, children: [_jsx("h3", { css: headerTitle, children: "AI Agent" }), !isVoiceMode && (_jsx("div", { css: css `
554
+ position: relative !important;
555
+ pointer-events: auto !important;
556
+ `, children: _jsx(ModelSelector, {}) })), !isVoiceMode && piiDisplayMode === 'redacted' && (_jsxs("div", { css: css `
557
+ display: inline-flex !important;
558
+ align-items: center !important;
559
+ gap: 4px !important;
560
+ padding: 2px 8px !important;
561
+ background-color: rgba(255, 255, 255, 0.2) !important;
562
+ border: 1px solid rgba(255, 255, 255, 0.4) !important;
563
+ border-radius: 10px !important;
564
+ font-size: 10px !important;
565
+ font-weight: 600 !important;
566
+ color: #ffffff !important;
567
+ white-space: nowrap !important;
568
+ line-height: 1.4 !important;
569
+ user-select: none !important;
570
+ `, title: "PII Protection is active \u2014 sensitive data is automatically anonymized before sending to AI", children: [_jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "#ffffff", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z" }) }), "PII Protected"] })), isAITLoading && (_jsxs("div", { css: css `
571
+ display: flex !important;
572
+ align-items: center !important;
573
+ gap: 5px !important;
574
+ font-size: 12px !important;
575
+ opacity: 0.8 !important;
576
+ `, children: [_jsx("div", { css: loadingSpinner }), "Loading..."] }))] }), _jsxs("div", { css: css `
577
+ display: flex !important;
578
+ align-items: center !important;
579
+ gap: 10px !important;
580
+ pointer-events: auto !important;
581
+ `, children: [_jsxs("button", { onClick: () => void ((isVoiceMode || isVoiceConnecting) ? exitVoiceMode() : enterVoiceMode()), css: [headerButton, css `
582
+ width: auto !important;
583
+ min-width: 82px !important;
584
+ padding: 0 10px !important;
585
+ gap: 6px !important;
586
+ font-size: 12px !important;
587
+ font-weight: 600 !important;
588
+ line-height: 1 !important;
589
+ white-space: nowrap !important;
590
+ `], title: isVoiceConnecting
591
+ ? 'Cancel voice connection and return to text mode'
592
+ : isVoiceMode
593
+ ? 'Switch to text mode'
594
+ : 'Switch to voice mode', onPointerDown: (e) => e.stopPropagation(), children: [_jsx(GraphicEqIcon, { css: css `font-size: 16px !important; color: #fff !important;` }), (isVoiceMode || isVoiceConnecting) ? 'Text Mode' : 'Voice Mode'] }), _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 `
595
+ font-size: 20px !important;
596
+ color: #f39c12 !important;
597
+ `, 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 `
598
+ background-color: #f8f9fa !important;
599
+ border: 1px solid #e9ecef !important;
600
+ margin: 16px !important;
601
+ padding: 12px !important;
602
+ border-radius: 8px !important;
603
+ text-align: center !important;
604
+ font-size: 13px !important;
605
+ color: #6c757d !important;
606
+ `, children: [_jsxs("span", { children: ["\uD83D\uDCA1 IDV suggestion hidden. Will show again in ", timeRemaining] }), _jsx("button", { onClick: () => {
607
+ localStorage.removeItem('idv-suggestion-dismissed');
608
+ setShowIDVSuggestion(true);
609
+ setDismissUntil(null);
610
+ setTimeRemaining('');
611
+ }, css: css `
612
+ background: none !important;
613
+ border: none !important;
614
+ color: #007bff !important;
615
+ font-size: 12px !important;
616
+ cursor: pointer !important;
617
+ margin-left: 8px !important;
618
+ text-decoration: underline !important;
619
+ `, title: "Show now", children: "Show now" })] })), _jsx(MessageList, {}), !isVoiceMode && _jsx(PresetMessages, {}), isVoiceMode ? _jsx(VoiceModePanel, {}) : _jsx(MessageInput, {})] }), showPermissionForm && (_jsx("div", { css: modalOverlay, onClick: (e) => {
620
+ // Close modal when clicking on background
621
+ if (e.target === e.currentTarget) {
622
+ setShowPermissionForm(false);
623
+ }
624
+ }, children: _jsx(PermissionForm, { onClose: () => setShowPermissionForm(false) }) })), notification.show && (_jsx(ToastNotification, { type: notification.type, message: notification.message, onClose: handleCloseNotification, isChatOpen: isOpen }))] }));
625
+ };
@@ -0,0 +1,3 @@
1
+ import * as React from 'react';
2
+ export declare const MessageInput: React.FC;
3
+ //# sourceMappingURL=MessageInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../../../src/legacy/chatbot/ui/MessageInput.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAofhC,CAAC"}