@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.1.2 → 0.1.4

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 (93) hide show
  1. package/dist/ChatBot.d.ts +5 -0
  2. package/dist/ChatBot.d.ts.map +1 -0
  3. package/dist/ChatBot.js +35 -0
  4. package/dist/assets/images/adiSideItalicDataUri.d.ts +2 -0
  5. package/dist/assets/images/adiSideItalicDataUri.d.ts.map +1 -0
  6. package/dist/assets/images/adiSideItalicDataUri.js +1 -0
  7. package/dist/context/AgentAssistantContext.d.ts.map +1 -1
  8. package/dist/context/AgentAssistantContext.js +18 -0
  9. package/dist/context/ChatBotContext.d.ts +5 -0
  10. package/dist/context/ChatBotContext.d.ts.map +1 -0
  11. package/dist/context/ChatBotContext.js +2908 -0
  12. package/dist/index.d.ts +5 -13
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +4 -11
  15. package/dist/legacy/chatbot/context/ChatBotContext.d.ts.map +1 -1
  16. package/dist/legacy/chatbot/context/ChatBotContext.js +14 -0
  17. package/dist/types/ChatBotTypes.d.ts +166 -0
  18. package/dist/types/ChatBotTypes.d.ts.map +1 -0
  19. package/dist/types/ChatBotTypes.js +1 -0
  20. package/dist/ui/BerifyMeModal.d.ts +17 -0
  21. package/dist/ui/BerifyMeModal.d.ts.map +1 -0
  22. package/dist/ui/BerifyMeModal.js +110 -0
  23. package/dist/ui/ChatBotHeader.d.ts +15 -0
  24. package/dist/ui/ChatBotHeader.d.ts.map +1 -0
  25. package/dist/ui/ChatBotHeader.js +62 -0
  26. package/dist/ui/ChatBotUI.d.ts +3 -0
  27. package/dist/ui/ChatBotUI.d.ts.map +1 -0
  28. package/dist/ui/ChatBotUI.js +557 -0
  29. package/dist/ui/MessageInput.d.ts +3 -0
  30. package/dist/ui/MessageInput.d.ts.map +1 -0
  31. package/dist/ui/MessageInput.js +321 -0
  32. package/dist/ui/MessageList.d.ts +4 -0
  33. package/dist/ui/MessageList.d.ts.map +1 -0
  34. package/dist/ui/MessageList.js +455 -0
  35. package/dist/ui/ModelSelector.d.ts +4 -0
  36. package/dist/ui/ModelSelector.d.ts.map +1 -0
  37. package/dist/ui/ModelSelector.js +122 -0
  38. package/dist/ui/NotificationModal.d.ts +15 -0
  39. package/dist/ui/NotificationModal.d.ts.map +1 -0
  40. package/dist/ui/NotificationModal.js +53 -0
  41. package/dist/ui/PermissionForm.d.ts +8 -0
  42. package/dist/ui/PermissionForm.d.ts.map +1 -0
  43. package/dist/ui/PermissionForm.js +465 -0
  44. package/dist/ui/PresetMessages.d.ts +4 -0
  45. package/dist/ui/PresetMessages.d.ts.map +1 -0
  46. package/dist/ui/PresetMessages.js +33 -0
  47. package/dist/ui/VoiceModePanel.d.ts +3 -0
  48. package/dist/ui/VoiceModePanel.d.ts.map +1 -0
  49. package/dist/ui/VoiceModePanel.js +95 -0
  50. package/dist/ui/chatBotHeaderParts.d.ts +15 -0
  51. package/dist/ui/chatBotHeaderParts.d.ts.map +1 -0
  52. package/dist/ui/chatBotHeaderParts.js +50 -0
  53. package/dist/ui/index.d.ts +9 -0
  54. package/dist/ui/index.d.ts.map +1 -0
  55. package/dist/ui/index.js +8 -0
  56. package/dist/ui/styles/isolatedStyles.d.ts +73 -0
  57. package/dist/ui/styles/isolatedStyles.d.ts.map +1 -0
  58. package/dist/ui/styles/isolatedStyles.js +985 -0
  59. package/package.json +3 -3
  60. package/src/{legacy/chatbot/context → context}/ChatBotContext.tsx +0 -1
  61. package/src/index.ts +17 -40
  62. package/src/{legacy/chatbot/ui → ui}/ModelSelector.tsx +1 -1
  63. package/src/{legacy/chatbot/ui → ui}/VoiceModePanel.tsx +1 -1
  64. package/src/ui/index.ts +8 -0
  65. package/src/NxtlinqAgentChat.tsx +0 -79
  66. package/src/components/AgentAssistantShell.tsx +0 -104
  67. package/src/components/AgentComposer.tsx +0 -134
  68. package/src/components/AgentMessageList.tsx +0 -78
  69. package/src/components/AgentRemoteAudio.tsx +0 -34
  70. package/src/components/AgentVoiceBar.tsx +0 -173
  71. package/src/components/PresetMessageChips.tsx +0 -41
  72. package/src/context/AgentAssistantContext.tsx +0 -278
  73. package/src/legacy/index.ts +0 -26
  74. package/src/theme/defaultTheme.ts +0 -22
  75. package/src/types.ts +0 -65
  76. package/src/voice/useVoiceConnectOrchestration.ts +0 -117
  77. package/src/voice/useVoiceMicState.ts +0 -117
  78. package/src/voice/useVoiceTranscriptMessages.ts +0 -188
  79. package/src/voice/voiceMicConstants.ts +0 -13
  80. package/src/voice/voiceUserBubble.ts +0 -71
  81. /package/src/{legacy/chatbot/ChatBot.tsx → ChatBot.tsx} +0 -0
  82. /package/src/{legacy/assets → assets}/images/adiSideItalicDataUri.ts +0 -0
  83. /package/src/{legacy/chatbot/types → types}/ChatBotTypes.ts +0 -0
  84. /package/src/{legacy/chatbot/ui → ui}/BerifyMeModal.tsx +0 -0
  85. /package/src/{legacy/chatbot/ui → ui}/ChatBotHeader.tsx +0 -0
  86. /package/src/{legacy/chatbot/ui → ui}/ChatBotUI.tsx +0 -0
  87. /package/src/{legacy/chatbot/ui → ui}/MessageInput.tsx +0 -0
  88. /package/src/{legacy/chatbot/ui → ui}/MessageList.tsx +0 -0
  89. /package/src/{legacy/chatbot/ui → ui}/NotificationModal.tsx +0 -0
  90. /package/src/{legacy/chatbot/ui → ui}/PermissionForm.tsx +0 -0
  91. /package/src/{legacy/chatbot/ui → ui}/PresetMessages.tsx +0 -0
  92. /package/src/{legacy/chatbot/ui → ui}/chatBotHeaderParts.tsx +0 -0
  93. /package/src/{legacy/chatbot/ui → ui}/styles/isolatedStyles.ts +0 -0
@@ -0,0 +1,455 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import * as React from 'react';
4
+ import { css } from '@emotion/react';
5
+ import { convertUrlsToLinks, walletTextUtils } from '@bytexbyte/nxtlinq-ai-agent-web-development';
6
+ import { useChatBot } from '../context/ChatBotContext';
7
+ import AttachFileIcon from '@mui/icons-material/AttachFile';
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, piiTokenReveal, } from './styles/isolatedStyles';
9
+ const piiTokenStatic = {
10
+ fontFamily: 'monospace',
11
+ fontWeight: 400,
12
+ display: 'inline',
13
+ };
14
+ /**
15
+ * Highlight PII tokens in anonymized content.
16
+ * When animate=true (live transition), each token gets a staggered glow animation.
17
+ * When animate=false (loaded from history), tokens are styled statically.
18
+ */
19
+ function highlightPiiTokens(text, animate = false) {
20
+ const parts = text.split(/(<?[A-Z][A-Z_]*_\d+>?)/g);
21
+ if (parts.length === 1)
22
+ return text;
23
+ let tokenIndex = 0;
24
+ return React.createElement(React.Fragment, null, ...parts.map((part, i) => {
25
+ if (/^<?[A-Z][A-Z_]*_\d+>?$/.test(part)) {
26
+ const idx = tokenIndex++;
27
+ if (animate) {
28
+ return React.createElement('span', {
29
+ key: i,
30
+ css: piiTokenReveal,
31
+ style: { animationDelay: `${idx * 0.25 + 0.6}s` },
32
+ }, part);
33
+ }
34
+ return React.createElement('span', { key: i, style: piiTokenStatic }, part);
35
+ }
36
+ return part;
37
+ }));
38
+ }
39
+ // ===== PII Bubble Header =====
40
+ /** Word-boundary match — prevents PERSON_1 from matching inside PERSON_11 */
41
+ function tokenInText(text, token) {
42
+ return new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(text);
43
+ }
44
+ const ShieldIcon = () => (_jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "currentColor", 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" }) }));
45
+ // --- Styles: all PII header styles co-located for maintainability ---
46
+ /** Scan line overlay on the blue bubble during scanning */
47
+ const piiScanOverlay = css `
48
+ overflow: hidden !important;
49
+ &::before {
50
+ content: '' !important;
51
+ position: absolute !important;
52
+ top: 0 !important;
53
+ left: 0 !important;
54
+ width: 100% !important;
55
+ height: 100% !important;
56
+ border-radius: inherit !important;
57
+ pointer-events: none !important;
58
+ z-index: 1 !important;
59
+ background: linear-gradient(
60
+ 90deg,
61
+ transparent 0%,
62
+ rgba(255,255,255,0.06) 30%,
63
+ rgba(255,255,255,0.18) 50%,
64
+ rgba(255,255,255,0.06) 70%,
65
+ transparent 100%
66
+ ) !important;
67
+ @keyframes piiScanLine {
68
+ 0% { transform: translateX(-100%); }
69
+ 100% { transform: translateX(100%); }
70
+ }
71
+ animation: piiScanLine 1.8s ease-in-out infinite !important;
72
+ }
73
+ `;
74
+ /** Content fade-in when text swaps from original → redacted */
75
+ const piiContentFadeIn = css `
76
+ @keyframes piiFadeIn {
77
+ 0% { opacity: 0.2; }
78
+ 100% { opacity: 1; }
79
+ }
80
+ animation: piiFadeIn 0.6s ease-out !important;
81
+ `;
82
+ const piiHeaderRow = css `
83
+ display: flex !important;
84
+ align-items: center !important;
85
+ gap: 5px !important;
86
+ font-size: 11px !important;
87
+ font-weight: 500 !important;
88
+ font-family: inherit !important;
89
+ margin-bottom: 6px !important;
90
+ padding-bottom: 5px !important;
91
+ border-bottom: 1px solid rgba(255,255,255,0.15) !important;
92
+ color: rgba(255,255,255,0.85) !important;
93
+ `;
94
+ const piiScanLabel = css `
95
+ @keyframes piiPulse {
96
+ 0%, 100% { opacity: 1; }
97
+ 50% { opacity: 0.5; }
98
+ }
99
+ display: inline-flex !important;
100
+ align-items: baseline !important;
101
+ animation: piiPulse 1.2s ease-in-out infinite !important;
102
+ `;
103
+ const piiBounceDot0 = css `
104
+ @keyframes piiBounce {
105
+ 0%, 100% { transform: translateY(0); }
106
+ 50% { transform: translateY(-3px); }
107
+ }
108
+ display: inline-block !important;
109
+ margin-left: 2px !important;
110
+ animation: piiBounce 0.6s ease-in-out infinite !important;
111
+ animation-delay: 0ms !important;
112
+ `;
113
+ const piiBounceDot1 = css `
114
+ display: inline-block !important;
115
+ animation: piiBounce 0.6s ease-in-out infinite !important;
116
+ animation-delay: 150ms !important;
117
+ `;
118
+ const piiBounceDot2 = css `
119
+ display: inline-block !important;
120
+ animation: piiBounce 0.6s ease-in-out infinite !important;
121
+ animation-delay: 300ms !important;
122
+ `;
123
+ /** Below-bubble green badge: "✓ {N} items protected ▸" */
124
+ const piiBelowBubbleBtn = css `
125
+ all: unset !important;
126
+ display: inline-flex !important;
127
+ align-items: center !important;
128
+ gap: 4px !important;
129
+ font-size: 11px !important;
130
+ font-weight: 600 !important;
131
+ font-family: inherit !important;
132
+ color: #2e7d32 !important;
133
+ background: #e8f5e9 !important;
134
+ border-radius: 12px !important;
135
+ padding: 3px 10px !important;
136
+ margin-top: 6px !important;
137
+ cursor: pointer !important;
138
+ &:hover { background: #c8e6c9 !important; }
139
+ `;
140
+ const piiNoPiiStyle = css `
141
+ @keyframes piiFadeOut {
142
+ 0% { opacity: 1; }
143
+ 70% { opacity: 1; }
144
+ 100% { opacity: 0; }
145
+ }
146
+ animation: piiFadeOut 2s ease-out forwards !important;
147
+ `;
148
+ /** Entity mapping panel — below bubble, original card style */
149
+ const piiEntityPanel = css `
150
+ margin-top: 6px !important;
151
+ padding: 8px 12px !important;
152
+ background-color: #fafafa !important;
153
+ border: 1px solid #e0e0e0 !important;
154
+ border-radius: 8px !important;
155
+ font-family: inherit !important;
156
+ `;
157
+ const piiEntityRowStyle = css `
158
+ display: flex !important;
159
+ align-items: center !important;
160
+ gap: 6px !important;
161
+ font-size: 11px !important;
162
+ line-height: 1.6 !important;
163
+ `;
164
+ const piiOriginalStyle = css `
165
+ color: #d32f2f !important;
166
+ text-decoration: line-through !important;
167
+ font-family: inherit !important;
168
+ `;
169
+ const piiArrowStyle = css `
170
+ color: #bdbdbd !important;
171
+ font-size: 10px !important;
172
+ `;
173
+ const piiTokenMappingStyle = css `
174
+ color: #1565c0 !important;
175
+ font-family: monospace !important;
176
+ font-weight: 600 !important;
177
+ `;
178
+ /** Green checkmark icon for below-bubble badge */
179
+ const GreenCheckIcon = () => (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "#2e7d32", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }));
180
+ /** Status-only header inside the bubble */
181
+ const PiiStatusHeader = React.memo(({ message }) => {
182
+ const getInitialPhase = () => {
183
+ if (message.piiStatus === 'complete' && message.piiProtection?.anonymizedContent)
184
+ return 'done';
185
+ if (message.piiStatus === 'none')
186
+ return 'faded';
187
+ if (message.piiStatus === 'scanning')
188
+ return 'scanning';
189
+ return 'idle';
190
+ };
191
+ const [phase, setPhase] = React.useState(getInitialPhase);
192
+ const prevStatusRef = React.useRef(message.piiStatus);
193
+ const timerRef = React.useRef(null);
194
+ React.useEffect(() => () => { if (timerRef.current)
195
+ clearTimeout(timerRef.current); }, []);
196
+ React.useEffect(() => {
197
+ const prev = prevStatusRef.current;
198
+ const curr = message.piiStatus;
199
+ prevStatusRef.current = curr;
200
+ if (timerRef.current) {
201
+ clearTimeout(timerRef.current);
202
+ timerRef.current = null;
203
+ }
204
+ if (curr === 'scanning' && prev !== 'scanning') {
205
+ setPhase('scanning');
206
+ }
207
+ else if (curr === 'complete') {
208
+ setPhase(message.piiProtection?.anonymizedContent ? 'done' : 'faded');
209
+ }
210
+ else if (curr === 'none') {
211
+ if (prev === 'scanning') {
212
+ setPhase('no-pii');
213
+ timerRef.current = setTimeout(() => setPhase('faded'), 2000);
214
+ }
215
+ else {
216
+ setPhase('faded');
217
+ }
218
+ }
219
+ }, [message.piiStatus, message.piiProtection?.anonymizedContent]);
220
+ if (phase === 'idle' || phase === 'faded')
221
+ return null;
222
+ if (phase === 'scanning') {
223
+ const step = message.piiStep ?? 0;
224
+ const label = step >= 1 ? 'Sending' : 'Scanning';
225
+ return (_jsxs("div", { css: piiHeaderRow, children: [_jsx(ShieldIcon, {}), _jsxs("span", { css: piiScanLabel, children: [label, _jsx("span", { css: piiBounceDot0, children: "." }), _jsx("span", { css: piiBounceDot1, children: "." }), _jsx("span", { css: piiBounceDot2, children: "." })] })] }));
226
+ }
227
+ if (phase === 'no-pii') {
228
+ return (_jsxs("div", { css: [piiHeaderRow, piiNoPiiStyle], children: [_jsx(ShieldIcon, {}), _jsx("span", { children: "No sensitive data found" })] }));
229
+ }
230
+ // Done: simple status label
231
+ return (_jsxs("div", { css: piiHeaderRow, children: [_jsx(ShieldIcon, {}), _jsx("span", { children: "\u2713 Protected" })] }));
232
+ });
233
+ export const MessageList = () => {
234
+ const { messages, isLoading, isTtsProcessing, requiresGesture, retryTtsWithGesture, connectWallet, signInWallet, hitAddress, isAutoConnecting, isNeedSignInWithWallet, enableAIT, isAITLoading, isAITEnabling, sendMessage, permissions, availableModels, serviceId, piiDisplayMode, } = useChatBot();
235
+ const messagesEndRef = React.useRef(null);
236
+ const scrollToBottom = () => {
237
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
238
+ };
239
+ React.useEffect(() => {
240
+ scrollToBottom();
241
+ }, [messages]);
242
+ // Track which message's PII entity panel is expanded (only one at a time)
243
+ const [expandedPiiMsgId, setExpandedPiiMsgId] = React.useState(null);
244
+ const handleButtonClick = async (buttonType, message) => {
245
+ // Prevent sending messages while AI Agent is processing
246
+ if (isLoading) {
247
+ return;
248
+ }
249
+ if (buttonType === 'connectWallet') {
250
+ connectWallet(true);
251
+ }
252
+ else if (buttonType === 'signIn') {
253
+ signInWallet(true);
254
+ }
255
+ else if (buttonType === 'enableAIT') {
256
+ const requiredPermission = message.metadata?.requiredPermission;
257
+ if (requiredPermission) {
258
+ const success = await enableAIT(requiredPermission);
259
+ if (success) {
260
+ // Find the last user message
261
+ const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
262
+ if (lastUserMsg && lastUserMsg.content) {
263
+ // Re-execute the previous user message (retryCount=1 to skip duplicate PII log)
264
+ await sendMessage(lastUserMsg.content, 1);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ else if (buttonType === 'continue') {
270
+ const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
271
+ if (lastUserMsg && lastUserMsg.content) {
272
+ // Re-execute the previous user message (retryCount=1 to skip duplicate PII log)
273
+ await sendMessage(lastUserMsg.content, 1);
274
+ }
275
+ }
276
+ };
277
+ // Get model display name from API-provided model list
278
+ const getModelDisplayName = (modelValue) => {
279
+ if (!modelValue)
280
+ return '';
281
+ // Special case: show "Adi" for specific serviceId
282
+ const targetServiceId = 'e48fc2b9-a7d1-49e3-85cb-9d621a0bf774';
283
+ if (serviceId && serviceId.trim() === targetServiceId) {
284
+ return 'Adi';
285
+ }
286
+ // Find model in the list returned by API
287
+ const model = availableModels.find(m => m.value === modelValue);
288
+ // Use label from API or fallback to value
289
+ return model?.label || modelValue;
290
+ };
291
+ return (_jsxs("div", { css: messageListContainer, children: [messages.map((message) => {
292
+ // Determine user message content based on piiStep
293
+ // Show redacted as soon as piiStep >= 1 ({N} protected) — data available
294
+ const showRedacted = message.role === 'user'
295
+ && piiDisplayMode === 'redacted'
296
+ && message.piiProtection?.anonymizedContent
297
+ && (message.piiStep !== undefined && message.piiStep >= 1);
298
+ // Is the bubble currently in scanning state? (for scan overlay animation)
299
+ const isUserScanning = message.role === 'user'
300
+ && piiDisplayMode === 'redacted'
301
+ && message.piiStatus === 'scanning';
302
+ const showPiiHeader = message.role === 'user'
303
+ && piiDisplayMode === 'redacted'
304
+ && !!message.piiStatus;
305
+ const isPiiExpanded = expandedPiiMsgId === message.id;
306
+ // Compute relevant entity mapping for this message
307
+ let relevantMapping = {};
308
+ if (showPiiHeader && message.piiProtection?.mapping && message.piiProtection?.anonymizedContent) {
309
+ const content = message.piiProtection.anonymizedContent;
310
+ for (const [original, token] of Object.entries(message.piiProtection.mapping)) {
311
+ if (tokenInText(content, token)) {
312
+ relevantMapping[original] = token;
313
+ }
314
+ }
315
+ }
316
+ const entityCount = Object.keys(relevantMapping).length;
317
+ const isAgentLikeStream = message.metadata?.agentLlmStream
318
+ || message.metadata?.voiceRealtime
319
+ || message.metadata?.model?.endsWith('-stream');
320
+ const isAgentStreamAwaitingFirstToken = message.role === 'assistant'
321
+ && message.isStreaming
322
+ && (message.metadata?.agentLlmStream || message.metadata?.model?.endsWith('-stream'))
323
+ && !(message.partialContent ?? '').length;
324
+ if (isAgentStreamAwaitingFirstToken) {
325
+ return null;
326
+ }
327
+ return (_jsxs("div", { css: message.role === 'user' ? userMessage : messageBubble, children: [_jsxs("div", { css: [
328
+ message.role === 'user'
329
+ ? userMessageContent
330
+ : message.metadata?.isRetry
331
+ ? retryMessageContent
332
+ : messageContent,
333
+ isUserScanning && piiScanOverlay,
334
+ ], children: [showPiiHeader && (_jsx(PiiStatusHeader, { message: message })), message.metadata?.isRetry && (_jsx("span", { css: css `margin-right: 8px !important; font-size: 14px !important;`, children: "\uD83D\uDD04" })), message.attachments && message.attachments.length > 0 && (_jsx("div", { css: css `
335
+ display: flex !important;
336
+ flex-wrap: wrap !important;
337
+ gap: 8px !important;
338
+ margin-bottom: ${message.content ? '10px' : '0'} !important;
339
+ `, children: message.attachments.map((attachment, idx) => (_jsxs("div", { css: css `
340
+ position: relative !important;
341
+ display: inline-block !important;
342
+ border: 1px solid #ddd !important;
343
+ border-radius: 8px !important;
344
+ overflow: hidden !important;
345
+ background: #f5f5f5 !important;
346
+ max-width: 200px !important;
347
+ `, children: [attachment.type === 'image' && (_jsx("img", { src: attachment.url, alt: attachment.name, css: css `
348
+ max-width: 200px !important;
349
+ max-height: 200px !important;
350
+ object-fit: cover !important;
351
+ display: block !important;
352
+ cursor: pointer !important;
353
+ `, onClick: () => {
354
+ // Open image in new window on click
355
+ const newWindow = window.open();
356
+ if (newWindow) {
357
+ newWindow.document.write(`<img src="${attachment.url}" style="max-width: 100%; height: auto;" />`);
358
+ }
359
+ } })), attachment.type === 'file' && (_jsxs("div", { css: css `
360
+ padding: 20px 15px !important;
361
+ text-align: center !important;
362
+ min-width: 150px !important;
363
+ display: flex !important;
364
+ align-items: center !important;
365
+ justify-content: center !important;
366
+ flex-direction: column !important;
367
+ gap: 8px !important;
368
+ `, children: [_jsx(AttachFileIcon, { css: css `font-size: 32px !important; color: #666 !important;` }), _jsx("span", { css: css `
369
+ font-size: 12px !important;
370
+ color: #666 !important;
371
+ word-break: break-word !important;
372
+ max-width: 120px !important;
373
+ `, children: attachment.name }), attachment.size && (_jsxs("span", { css: css `
374
+ font-size: 10px !important;
375
+ color: #999 !important;
376
+ `, children: [(attachment.size / 1024).toFixed(1), " KB"] }))] }))] }, idx))) })), message.isStreaming && message.metadata?.voiceRealtime
377
+ && (message.partialContent ?? '').length > 0 && (_jsxs("div", { css: streamingPartialText, children: [message.role === 'assistant'
378
+ ? convertUrlsToLinks(message.partialContent ?? '')
379
+ : message.partialContent, _jsx("span", { css: streamingCaret, children: "\u258A" })] })), message.isStreaming && message.role === 'assistant'
380
+ && (message.metadata?.agentLlmStream || message.metadata?.model?.endsWith('-stream'))
381
+ && !message.metadata?.voiceRealtime
382
+ && (message.partialContent ?? '').length > 0 && (_jsxs("div", { css: streamingPartialText, children: [convertUrlsToLinks(message.partialContent ?? ''), _jsx("span", { css: streamingCaret, children: "\u258A" })] })), message.isStreaming && message.partialContent
383
+ && !message.metadata?.voiceRealtime
384
+ && !(message.metadata?.agentLlmStream || message.metadata?.model?.endsWith('-stream')) && (_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'
385
+ && !isAgentLikeStream && (_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 && (() => {
386
+ // User message with redacted PII available — show tokens with fade-in
387
+ if (showRedacted) {
388
+ const isLive = message.piiStatus === 'scanning';
389
+ const content = highlightPiiTokens(message.piiProtection.anonymizedContent, isLive);
390
+ return isLive
391
+ ? _jsx("span", { css: piiContentFadeIn, children: content })
392
+ : content;
393
+ }
394
+ // User message during scanning (step 0) — show original text (scan overlay handles animation)
395
+ if (isUserScanning && !showRedacted) {
396
+ return message.content;
397
+ }
398
+ // Assistant message with redacted PII
399
+ if (message.role === 'assistant'
400
+ && piiDisplayMode === 'redacted'
401
+ && message.piiProtection?.anonymizedContent) {
402
+ return highlightPiiTokens(message.piiProtection.anonymizedContent, false);
403
+ }
404
+ // Default
405
+ return message.role === 'assistant'
406
+ ? convertUrlsToLinks(message.content)
407
+ : message.content;
408
+ })(), message.button && (_jsx("div", { css: css `margin-top: 10px !important;`, children: _jsx("button", { onClick: () => {
409
+ if (message.button && message.button.trim()) {
410
+ handleButtonClick(message.button, message);
411
+ }
412
+ }, disabled: isAutoConnecting ||
413
+ (message.button === 'connectWallet' && Boolean(hitAddress)) ||
414
+ (message.button === 'signIn' && !isNeedSignInWithWallet) ||
415
+ (message.button === 'enableAIT' && (isAITLoading || isAITEnabling ||
416
+ (message.metadata?.requiredPermission && permissions.includes(message.metadata.requiredPermission)))) || false, css: (message.button === 'connectWallet' && Boolean(hitAddress)) ? connectedButton :
417
+ (message.button === 'signIn' && !isNeedSignInWithWallet) ? connectedButton :
418
+ (message.button === 'continue') ? connectedButton :
419
+ (message.button === 'enableAIT' && (message.metadata?.requiredPermission && permissions.includes(message.metadata.requiredPermission))) ? connectedButton :
420
+ chatbotButton, children: isAutoConnecting ? 'Connecting...' :
421
+ message.button === 'connectWallet' ? (Boolean(hitAddress) ? 'Connected' : walletTextUtils.getWalletText('Connect Wallet', serviceId)) :
422
+ message.button === 'signIn' ? (!isNeedSignInWithWallet ? 'Signed In' : 'Sign In') :
423
+ message.button === 'continue' ? 'Continue' :
424
+ message.button === 'enableAIT' ?
425
+ ((isAITLoading || isAITEnabling) ? 'Enabling...' :
426
+ (message.metadata?.requiredPermission && permissions.includes(message.metadata.requiredPermission)) ? 'AIT Enabled' : 'Enable AIT Permissions') :
427
+ message.button }) }))] }), showPiiHeader && message.piiStatus === 'complete' && entityCount > 0 && (_jsxs("div", { css: css `text-align: right !important;`, children: [_jsxs("button", { css: piiBelowBubbleBtn, onClick: () => setExpandedPiiMsgId(v => v === message.id ? null : message.id), type: "button", children: [_jsx(GreenCheckIcon, {}), entityCount, " item", entityCount !== 1 ? 's' : '', " protected ", isPiiExpanded ? '▴' : '▸'] }), isPiiExpanded && (_jsx("div", { css: piiEntityPanel, children: Object.entries(relevantMapping).map(([original, token]) => (_jsxs("div", { css: piiEntityRowStyle, children: [_jsx("span", { css: piiOriginalStyle, children: original }), _jsx("span", { css: piiArrowStyle, children: "\u2192" }), _jsx("span", { css: piiTokenMappingStyle, children: token })] }, original))) }))] })), message.role === 'assistant' && message.metadata?.model && (_jsxs("div", { css: css `
428
+ ${modelIndicator}
429
+ gap: 8px !important;
430
+ `, children: [_jsxs("div", { css: modelBadge, children: [_jsx("span", { css: modelDot }), getModelDisplayName(message.metadata.model)] }), (isTtsProcessing || requiresGesture) && !isLoading && message.id === messages[messages.length - 1]?.id && (_jsxs("div", { css: css `
431
+ display: flex !important;
432
+ align-items: center !important;
433
+ font-size: 12px !important;
434
+ color: #666 !important;
435
+ gap: 4px !important;
436
+ `, children: [_jsx("span", { role: "img", "aria-hidden": "true", children: "\uD83D\uDD0A" }), _jsx("span", { children: requiresGesture ? (_jsx("button", { onClick: () => retryTtsWithGesture && retryTtsWithGesture(), css: css `
437
+ background: none !important;
438
+ border: none !important;
439
+ color: #1976d2 !important;
440
+ padding: 0 !important;
441
+ text-decoration: underline !important;
442
+ cursor: pointer !important;
443
+ `, children: "Tap to play voice" })) : 'Preparing voice reply...' })] }))] }))] }, message.id));
444
+ }), isLoading && (() => {
445
+ // During PII pipeline (step 0-1), hide "Thinking..." — message hasn't been sent to AI yet.
446
+ // Only show once Send step (2) is reached, meaning the anonymized message was sent to AI.
447
+ if (piiDisplayMode === 'redacted') {
448
+ const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
449
+ if (lastUserMsg?.piiStatus === 'scanning' && (lastUserMsg.piiStep === undefined || lastUserMsg.piiStep < 2)) {
450
+ return null;
451
+ }
452
+ }
453
+ return (_jsx("div", { css: loadingIndicator, children: "Thinking..." }));
454
+ })(), _jsx("div", { ref: messagesEndRef })] }));
455
+ };
@@ -0,0 +1,4 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import * as React from 'react';
3
+ export declare const ModelSelector: React.FC;
4
+ //# sourceMappingURL=ModelSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelSelector.d.ts","sourceRoot":"","sources":["../../src/ui/ModelSelector.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAsLjC,CAAC"}
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
+ /** @jsxImportSource @emotion/react */
3
+ import * as React from 'react';
4
+ import { css } from '@emotion/react';
5
+ import { useChatBot } from '../context/ChatBotContext';
6
+ import { walletTextUtils } from '@bytexbyte/nxtlinq-ai-agent-web-development';
7
+ import { adiSideItalicDataUri } from '../assets/images/adiSideItalicDataUri';
8
+ export const ModelSelector = () => {
9
+ const { availableModels, selectedModelIndex, handleModelChange, serviceId } = useChatBot();
10
+ // Safety check: ensure selectedModelIndex is within bounds
11
+ const safeSelectedModelIndex = selectedModelIndex >= availableModels.length ? 0 : selectedModelIndex;
12
+ // Check if current service is Adilas
13
+ const isAdilasService = walletTextUtils.isAdilasService(serviceId);
14
+ // Get fallback model label (first available model or 'Unknown')
15
+ // For Adilas, we don't show the label, only the image
16
+ const fallbackModelLabel = availableModels.length > 0
17
+ ? availableModels[0].label
18
+ : 'Unknown';
19
+ const [anchorEl, setAnchorEl] = React.useState(null);
20
+ const open = Boolean(anchorEl);
21
+ const handleClick = (event) => {
22
+ setAnchorEl(event.currentTarget);
23
+ };
24
+ const handleMenuItemClick = (event, index) => {
25
+ handleModelChange(index);
26
+ setAnchorEl(null);
27
+ };
28
+ const handleClose = () => {
29
+ setAnchorEl(null);
30
+ };
31
+ // Don't show anything if no models available
32
+ if (availableModels.length === 0) {
33
+ return null;
34
+ }
35
+ // For Adilas: only show image, no text (Adilas always has only 1 model)
36
+ if (isAdilasService) {
37
+ return (_jsx("div", { css: css `
38
+ display: flex !important;
39
+ align-items: center !important;
40
+ gap: 6px !important;
41
+ `, children: _jsx("div", { css: css `
42
+ background-color: white !important;
43
+ border-radius: 4px !important;
44
+ padding: 2px !important;
45
+ display: flex !important;
46
+ align-items: center !important;
47
+ justify-content: center !important;
48
+ `, children: _jsx("img", { src: adiSideItalicDataUri, alt: "Adi", css: css `
49
+ width: 28px !important;
50
+ height: 28px !important;
51
+ display: block !important;
52
+ ` }) }) }));
53
+ }
54
+ // For other services: handle single or multiple models
55
+ // If only 1 model, show model name
56
+ if (availableModels.length === 1) {
57
+ return (_jsx("div", { css: css `
58
+ display: flex !important;
59
+ align-items: center !important;
60
+ gap: 6px !important;
61
+ `, children: _jsx("div", { css: css `
62
+ display: flex !important;
63
+ align-items: center !important;
64
+ padding: 4px 8px !important;
65
+ border-radius: 4px !important;
66
+ background-color: rgba(255, 255, 255, 0.1) !important;
67
+ font-size: 12px !important;
68
+ font-weight: 500 !important;
69
+ `, children: _jsx("span", { children: availableModels[0].label }) }) }));
70
+ }
71
+ // If 2+ models, show full selector with dropdown
72
+ return (_jsxs("div", { css: css `
73
+ display: flex !important;
74
+ align-items: center !important;
75
+ position: relative !important;
76
+ gap: 6px !important;
77
+ `, children: [_jsxs("div", { css: css `
78
+ display: flex !important;
79
+ align-items: center !important;
80
+ cursor: pointer !important;
81
+ padding: 4px 8px !important;
82
+ border-radius: 4px !important;
83
+ background-color: rgba(255, 255, 255, 0.1) !important;
84
+ transition: background-color 0.2s !important;
85
+ font-size: 12px !important;
86
+ font-weight: 500 !important;
87
+ gap: 6px !important;
88
+
89
+ &:hover {
90
+ background-color: rgba(255, 255, 255, 0.2) !important;
91
+ }
92
+ `, onClick: handleClick, title: "Change AI Model", children: [_jsx("span", { children: availableModels[safeSelectedModelIndex]?.label || fallbackModelLabel }), _jsx("span", { css: css `font-size: 10px !important;`, children: "\u25BC" })] }), open && (_jsx("div", { css: css `
93
+ position: absolute !important;
94
+ top: 100% !important;
95
+ left: 0 !important;
96
+ background-color: white !important;
97
+ border: 1px solid #ddd !important;
98
+ border-radius: 4px !important;
99
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
100
+ z-index: 1000 !important;
101
+ min-width: 120px !important;
102
+ margin-top: 4px !important;
103
+ `, children: availableModels.map((model, index) => (_jsx("div", { css: css `
104
+ padding: 8px 12px !important;
105
+ cursor: pointer !important;
106
+ background-color: ${index === safeSelectedModelIndex ? '#f0f0f0' : 'transparent'} !important;
107
+ color: ${index === safeSelectedModelIndex ? '#007bff' : '#333'} !important;
108
+ font-size: 12px !important;
109
+ border-bottom: ${index < availableModels.length - 1 ? '1px solid #eee' : 'none'} !important;
110
+
111
+ &:hover {
112
+ background-color: ${index === safeSelectedModelIndex ? '#f0f0f0' : '#f8f9fa'} !important;
113
+ }
114
+ `, onClick: (event) => handleMenuItemClick(event, index), children: model.label }, model.value))) })), open && (_jsx("div", { css: css `
115
+ position: fixed !important;
116
+ top: 0 !important;
117
+ left: 0 !important;
118
+ right: 0 !important;
119
+ bottom: 0 !important;
120
+ z-index: 999 !important;
121
+ `, onClick: handleClose }))] }));
122
+ };
@@ -0,0 +1,15 @@
1
+ /** @jsxImportSource @emotion/react */
2
+ import * as React from 'react';
3
+ interface NotificationModalProps {
4
+ type: 'success' | 'error' | 'info' | 'warning';
5
+ title: string;
6
+ message: string;
7
+ onClose: () => void;
8
+ onConfirm?: () => void;
9
+ showConfirm?: boolean;
10
+ confirmText?: string;
11
+ cancelText?: string;
12
+ }
13
+ export declare const NotificationModal: React.FC<NotificationModalProps>;
14
+ export {};
15
+ //# sourceMappingURL=NotificationModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationModal.d.ts","sourceRoot":"","sources":["../../src/ui/NotificationModal.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,UAAU,sBAAsB;IAC9B,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA4F9D,CAAC"}