@paymanai/payman-ask-sdk 2.0.6 → 4.0.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.
@@ -1,4869 +0,0 @@
1
- 'use strict';
2
-
3
- var React = require('react');
4
- var reactNative = require('react-native');
5
- var paymanTypescriptAskSdk = require('@paymanai/payman-typescript-ask-sdk');
6
- var lucideReactNative = require('lucide-react-native');
7
- var jsxRuntime = require('react/jsx-runtime');
8
- var Markdown = require('react-native-markdown-display');
9
- var lucideReact = require('lucide-react');
10
- var framerMotion = require('framer-motion');
11
- var reactDom = require('react-dom');
12
- var clsx = require('clsx');
13
- var tailwindMerge = require('tailwind-merge');
14
- var Sentry = require('@sentry/react');
15
-
16
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
-
18
- function _interopNamespace(e) {
19
- if (e && e.__esModule) return e;
20
- var n = Object.create(null);
21
- if (e) {
22
- Object.keys(e).forEach(function (k) {
23
- if (k !== 'default') {
24
- var d = Object.getOwnPropertyDescriptor(e, k);
25
- Object.defineProperty(n, k, d.get ? d : {
26
- enumerable: true,
27
- get: function () { return e[k]; }
28
- });
29
- }
30
- });
31
- }
32
- n.default = e;
33
- return Object.freeze(n);
34
- }
35
-
36
- var React__default = /*#__PURE__*/_interopDefault(React);
37
- var Markdown__default = /*#__PURE__*/_interopDefault(Markdown);
38
- var Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
39
-
40
- // src/components/PaymanChat/index.native.tsx
41
- var PAYMAN_TEAL = "#00858d";
42
- var SEND_BUTTON_COLOR = "#15687d";
43
- var SEND_BUTTON_DISABLED_COLOR = "#d1d5db";
44
- var INPUT_BOTTOM_MARGIN = reactNative.Platform.OS === "ios" ? 18 : 8;
45
- var LIGHT_PALETTE = {
46
- surface: "#ffffff",
47
- soft: "#f4f4f5",
48
- border: "rgba(15,23,42,0.08)",
49
- borderFocused: "rgba(15,23,42,0.16)",
50
- textPrimary: "#0f172a",
51
- textTertiary: "#94a3b8",
52
- iconGrey: "#64748b",
53
- disclaimer: "#94a3b8"
54
- };
55
- var DARK_PALETTE = {
56
- surface: "#0d1719",
57
- soft: "#162022",
58
- border: "rgba(255,255,255,0.08)",
59
- borderFocused: "rgba(255,255,255,0.18)",
60
- textPrimary: "#f0f9fa",
61
- textTertiary: "rgba(240,249,250,0.40)",
62
- iconGrey: "rgba(240,249,250,0.55)",
63
- disclaimer: "rgba(240,249,250,0.38)"
64
- };
65
- var INPUT_FONT_SIZE = 15;
66
- var INPUT_LINE_HEIGHT = 22;
67
- var INPUT_MAX_HEIGHT = INPUT_LINE_HEIGHT * 8;
68
- var ANDROID_RIPPLE_DARK = { color: "rgba(15,23,42,0.10)", borderless: false };
69
- var ANDROID_RIPPLE_LIGHT = { color: "rgba(255,255,255,0.20)", borderless: false };
70
- var ChatInputV2 = React.forwardRef(
71
- function ChatInputV22({
72
- onSend,
73
- disabled = false,
74
- isStreaming = false,
75
- placeholder = "Type your message\u2026",
76
- enableVoice = false,
77
- voiceAvailable = false,
78
- isRecording = false,
79
- onVoicePress,
80
- showResetSession = false,
81
- onResetSession,
82
- hideSendButton = false,
83
- showAttachmentButton,
84
- showUploadImageButton = true,
85
- showAttachFileButton = true,
86
- onUploadImageClick,
87
- onAttachFileClick,
88
- hideDisclaimer = false,
89
- theme = "light",
90
- topAccessory
91
- }, ref) {
92
- const p = theme === "dark" ? DARK_PALETTE : LIGHT_PALETTE;
93
- const [value, setValue] = React.useState("");
94
- const [isFocused, setIsFocused] = React.useState(false);
95
- const [isKeyboardVisible, setIsKeyboardVisible] = React.useState(false);
96
- const [sheetOpen, setSheetOpen] = React.useState(false);
97
- const textInputRef = React.useRef(null);
98
- const hasAttachmentOptions = !!onUploadImageClick && showUploadImageButton || !!onAttachFileClick && showAttachFileButton;
99
- const showAttachmentMenuButton = (showAttachmentButton ?? true) && hasAttachmentOptions;
100
- React.useImperativeHandle(
101
- ref,
102
- () => ({
103
- setDraft: (text) => {
104
- setValue(text);
105
- requestAnimationFrame(() => textInputRef.current?.focus());
106
- },
107
- focus: () => textInputRef.current?.focus()
108
- }),
109
- []
110
- );
111
- const handleSend = React.useCallback(() => {
112
- const trimmed = value.trim();
113
- if (!trimmed || disabled) return;
114
- onSend(trimmed);
115
- setValue("");
116
- }, [value, disabled, onSend]);
117
- const canSend = value.trim().length > 0 && !disabled;
118
- const sendDisabled = !canSend || isStreaming;
119
- const showVoiceButton = enableVoice && !!onVoicePress;
120
- const isVoiceButtonDisabled = disabled || !voiceAvailable;
121
- const hasTopAccessory = topAccessory != null;
122
- const sendScale = React.useRef(new reactNative.Animated.Value(1)).current;
123
- React.useEffect(() => {
124
- reactNative.Animated.spring(sendScale, {
125
- toValue: canSend && !isStreaming ? 1 : 0.94,
126
- useNativeDriver: true,
127
- friction: 6,
128
- tension: 180
129
- }).start();
130
- }, [canSend, isStreaming, sendScale]);
131
- const handleAttachmentPress = React.useCallback(() => {
132
- if (disabled || isRecording) return;
133
- setSheetOpen(true);
134
- }, [disabled, isRecording]);
135
- const closeSheet = React.useCallback(() => setSheetOpen(false), []);
136
- React.useEffect(() => {
137
- const showEvent = reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
138
- const hideEvent = reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
139
- const showSub = reactNative.Keyboard.addListener(showEvent, () => {
140
- setIsKeyboardVisible(true);
141
- });
142
- const hideSub = reactNative.Keyboard.addListener(hideEvent, () => {
143
- setIsKeyboardVisible(false);
144
- });
145
- return () => {
146
- showSub.remove();
147
- hideSub.remove();
148
- };
149
- }, []);
150
- const handlePickImage = React.useCallback(() => {
151
- closeSheet();
152
- onUploadImageClick?.();
153
- }, [closeSheet, onUploadImageClick]);
154
- const handlePickFile = React.useCallback(() => {
155
- closeSheet();
156
- onAttachFileClick?.();
157
- }, [closeSheet, onAttachFileClick]);
158
- return /* @__PURE__ */ jsxRuntime.jsxs(
159
- reactNative.View,
160
- {
161
- style: [
162
- s.outer,
163
- {
164
- backgroundColor: p.surface,
165
- marginBottom: isKeyboardVisible ? 0 : INPUT_BOTTOM_MARGIN
166
- }
167
- ],
168
- children: [
169
- hasTopAccessory ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.topAccessory, children: topAccessory }) : null,
170
- /* @__PURE__ */ jsxRuntime.jsxs(
171
- reactNative.View,
172
- {
173
- style: [
174
- s.wrapper,
175
- hasTopAccessory && s.wrapperWithTopAccessory,
176
- { backgroundColor: p.soft, borderColor: p.border },
177
- (isFocused || isRecording) && { borderColor: p.borderFocused },
178
- disabled && s.wrapperDisabled
179
- ],
180
- children: [
181
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s.inputRow, children: /* @__PURE__ */ jsxRuntime.jsx(
182
- reactNative.TextInput,
183
- {
184
- ref: textInputRef,
185
- style: [s.input, { color: p.textPrimary }],
186
- value,
187
- onChangeText: (t) => {
188
- if (isRecording) return;
189
- setValue(t);
190
- },
191
- onFocus: () => setIsFocused(true),
192
- onBlur: () => setIsFocused(false),
193
- placeholder: isRecording ? "Listening\u2026" : placeholder,
194
- placeholderTextColor: p.textTertiary,
195
- multiline: true,
196
- editable: !disabled && !isRecording,
197
- returnKeyType: "default",
198
- blurOnSubmit: false,
199
- scrollEnabled: true
200
- }
201
- ) }),
202
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.controls, children: [
203
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.leftControls, children: [
204
- showResetSession && onResetSession ? /* @__PURE__ */ jsxRuntime.jsx(
205
- reactNative.Pressable,
206
- {
207
- onPress: onResetSession,
208
- disabled: isStreaming || isRecording,
209
- style: [
210
- s.iconBtn,
211
- (isStreaming || isRecording) && s.iconBtnDisabled
212
- ],
213
- android_ripple: ANDROID_RIPPLE_DARK,
214
- hitSlop: 6,
215
- accessibilityLabel: "New session",
216
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.RotateCcw, { size: 18, color: p.iconGrey, strokeWidth: 2 })
217
- }
218
- ) : null,
219
- showAttachmentMenuButton ? /* @__PURE__ */ jsxRuntime.jsx(
220
- reactNative.Pressable,
221
- {
222
- onPress: handleAttachmentPress,
223
- disabled: disabled || isRecording,
224
- style: [
225
- s.iconBtn,
226
- (disabled || isRecording) && s.iconBtnDisabled
227
- ],
228
- android_ripple: ANDROID_RIPPLE_DARK,
229
- hitSlop: 6,
230
- accessibilityLabel: "Attach",
231
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Plus, { size: 20, color: p.iconGrey, strokeWidth: 2 })
232
- }
233
- ) : null
234
- ] }),
235
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s.rightControls, children: [
236
- showVoiceButton ? /* @__PURE__ */ jsxRuntime.jsx(
237
- reactNative.Pressable,
238
- {
239
- onPress: onVoicePress,
240
- disabled: isVoiceButtonDisabled,
241
- style: [
242
- s.iconBtn,
243
- isRecording && s.iconBtnRecording,
244
- isVoiceButtonDisabled && s.iconBtnDisabled
245
- ],
246
- android_ripple: ANDROID_RIPPLE_DARK,
247
- hitSlop: 6,
248
- accessibilityLabel: isRecording ? "Stop recording" : "Voice input",
249
- children: /* @__PURE__ */ jsxRuntime.jsx(
250
- lucideReactNative.Mic,
251
- {
252
- size: 18,
253
- color: isRecording ? PAYMAN_TEAL : p.iconGrey,
254
- strokeWidth: 2
255
- }
256
- )
257
- }
258
- ) : null,
259
- !hideSendButton ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: { transform: [{ scale: sendScale }] }, children: /* @__PURE__ */ jsxRuntime.jsx(
260
- reactNative.Pressable,
261
- {
262
- onPress: handleSend,
263
- disabled: sendDisabled,
264
- style: [s.sendBtn, sendDisabled && s.sendBtnIdle],
265
- android_ripple: ANDROID_RIPPLE_LIGHT,
266
- hitSlop: 4,
267
- accessibilityLabel: "Send",
268
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ArrowUp, { size: 18, color: "#fff", strokeWidth: 2.75 })
269
- }
270
- ) }) : null
271
- ] })
272
- ] })
273
- ]
274
- }
275
- ),
276
- !hideDisclaimer ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s.disclaimer, { color: p.disclaimer }], children: "AI can make mistakes. Please double-check responses." }) : null,
277
- /* @__PURE__ */ jsxRuntime.jsx(
278
- AttachmentSheet,
279
- {
280
- visible: sheetOpen,
281
- onClose: closeSheet,
282
- showImage: !!onUploadImageClick && showUploadImageButton,
283
- showFile: !!onAttachFileClick && showAttachFileButton,
284
- onPickImage: handlePickImage,
285
- onPickFile: handlePickFile
286
- }
287
- )
288
- ]
289
- }
290
- );
291
- }
292
- );
293
- function AttachmentSheet({
294
- visible,
295
- onClose,
296
- showImage,
297
- showFile,
298
- onPickImage,
299
- onPickFile
300
- }) {
301
- const translateY = React.useRef(new reactNative.Animated.Value(80)).current;
302
- const opacity = React.useRef(new reactNative.Animated.Value(0)).current;
303
- React.useEffect(() => {
304
- if (visible) {
305
- reactNative.Animated.parallel([
306
- reactNative.Animated.timing(opacity, {
307
- toValue: 1,
308
- duration: 180,
309
- useNativeDriver: true
310
- }),
311
- reactNative.Animated.spring(translateY, {
312
- toValue: 0,
313
- useNativeDriver: true,
314
- friction: 9,
315
- tension: 90
316
- })
317
- ]).start();
318
- } else {
319
- translateY.setValue(80);
320
- opacity.setValue(0);
321
- }
322
- }, [visible, opacity, translateY]);
323
- return /* @__PURE__ */ jsxRuntime.jsxs(
324
- reactNative.Modal,
325
- {
326
- visible,
327
- transparent: true,
328
- animationType: "none",
329
- onRequestClose: onClose,
330
- children: [
331
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [sheet.backdrop, { opacity }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: sheet.backdropPressable, onPress: onClose }) }),
332
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: sheet.sheetWrap, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
333
- reactNative.Animated.View,
334
- {
335
- style: [
336
- sheet.sheet,
337
- { transform: [{ translateY }], opacity }
338
- ],
339
- children: [
340
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: sheet.handle }),
341
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sheet.headerRow, children: [
342
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sheet.title, children: "Add to message" }),
343
- /* @__PURE__ */ jsxRuntime.jsx(
344
- reactNative.Pressable,
345
- {
346
- onPress: onClose,
347
- hitSlop: 8,
348
- style: sheet.closeBtn,
349
- android_ripple: ANDROID_RIPPLE_DARK,
350
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.X, { size: 18, color: "#6b6b67", strokeWidth: 2.25 })
351
- }
352
- )
353
- ] }),
354
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: sheet.options, children: [
355
- showImage ? /* @__PURE__ */ jsxRuntime.jsxs(
356
- reactNative.Pressable,
357
- {
358
- onPress: onPickImage,
359
- style: sheet.option,
360
- android_ripple: ANDROID_RIPPLE_DARK,
361
- children: [
362
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: sheet.optionIconWrap, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ImagePlus, { size: 20, color: PAYMAN_TEAL, strokeWidth: 2 }) }),
363
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
364
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sheet.optionTitle, children: "Upload image" }),
365
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sheet.optionSubtitle, children: "From your camera roll" })
366
- ] })
367
- ]
368
- }
369
- ) : null,
370
- showFile ? /* @__PURE__ */ jsxRuntime.jsxs(
371
- reactNative.Pressable,
372
- {
373
- onPress: onPickFile,
374
- style: sheet.option,
375
- android_ripple: ANDROID_RIPPLE_DARK,
376
- children: [
377
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: sheet.optionIconWrap, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Paperclip, { size: 20, color: PAYMAN_TEAL, strokeWidth: 2 }) }),
378
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
379
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sheet.optionTitle, children: "Attach file" }),
380
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: sheet.optionSubtitle, children: "PDFs, documents and more" })
381
- ] })
382
- ]
383
- }
384
- ) : null
385
- ] })
386
- ]
387
- }
388
- ) })
389
- ]
390
- }
391
- );
392
- }
393
- var s = reactNative.StyleSheet.create({
394
- outer: {
395
- paddingHorizontal: 12,
396
- paddingTop: 6,
397
- paddingBottom: 6
398
- },
399
- topAccessory: {
400
- marginBottom: -1,
401
- zIndex: 1
402
- },
403
- wrapper: {
404
- borderRadius: 24,
405
- borderWidth: 1,
406
- paddingHorizontal: 6,
407
- paddingTop: 2,
408
- paddingBottom: 6
409
- },
410
- wrapperWithTopAccessory: {
411
- borderTopLeftRadius: 0,
412
- borderTopRightRadius: 0
413
- },
414
- wrapperFocused: {
415
- ...reactNative.Platform.select({
416
- ios: {
417
- shadowColor: "#000",
418
- shadowOpacity: 0.04,
419
- shadowRadius: 10,
420
- shadowOffset: { width: 0, height: 2 }
421
- },
422
- android: { elevation: 0 }
423
- })
424
- },
425
- wrapperDisabled: { opacity: 0.6 },
426
- inputRow: {
427
- maxHeight: INPUT_MAX_HEIGHT,
428
- paddingHorizontal: 12,
429
- paddingTop: 10,
430
- paddingBottom: 4
431
- },
432
- input: {
433
- fontSize: INPUT_FONT_SIZE,
434
- lineHeight: INPUT_LINE_HEIGHT,
435
- includeFontPadding: false,
436
- textAlignVertical: "top",
437
- padding: 0,
438
- margin: 0
439
- },
440
- controls: {
441
- flexDirection: "row",
442
- alignItems: "center",
443
- justifyContent: "space-between",
444
- paddingHorizontal: 2,
445
- paddingTop: 2
446
- },
447
- leftControls: {
448
- flexDirection: "row",
449
- alignItems: "center",
450
- gap: 2
451
- },
452
- rightControls: {
453
- flexDirection: "row",
454
- alignItems: "center",
455
- gap: 2
456
- },
457
- iconBtn: {
458
- width: 32,
459
- height: 32,
460
- borderRadius: 16,
461
- alignItems: "center",
462
- justifyContent: "center"
463
- },
464
- iconBtnDisabled: { opacity: 0.4 },
465
- iconBtnRecording: { backgroundColor: "rgba(0,133,141,0.10)" },
466
- sendBtn: {
467
- width: 34,
468
- height: 34,
469
- borderRadius: 17,
470
- backgroundColor: SEND_BUTTON_COLOR,
471
- alignItems: "center",
472
- justifyContent: "center",
473
- marginLeft: 2,
474
- ...reactNative.Platform.select({
475
- ios: {
476
- shadowColor: SEND_BUTTON_COLOR,
477
- shadowOpacity: 0.32,
478
- shadowRadius: 10,
479
- shadowOffset: { width: 0, height: 4 }
480
- },
481
- android: { elevation: 3 }
482
- })
483
- },
484
- sendBtnIdle: {
485
- backgroundColor: SEND_BUTTON_DISABLED_COLOR,
486
- opacity: 1,
487
- shadowOpacity: 0
488
- },
489
- disclaimer: {
490
- fontSize: 11,
491
- textAlign: "center",
492
- marginTop: 8,
493
- paddingHorizontal: 16
494
- }
495
- });
496
- var sheet = reactNative.StyleSheet.create({
497
- backdrop: {
498
- ...reactNative.StyleSheet.absoluteFillObject,
499
- backgroundColor: "rgba(15,23,42,0.35)"
500
- },
501
- backdropPressable: { flex: 1 },
502
- sheetWrap: {
503
- ...reactNative.StyleSheet.absoluteFillObject,
504
- justifyContent: "flex-end"
505
- },
506
- sheet: {
507
- backgroundColor: "#ffffff",
508
- borderTopLeftRadius: 24,
509
- borderTopRightRadius: 24,
510
- paddingHorizontal: 20,
511
- paddingTop: 8,
512
- paddingBottom: 36,
513
- ...reactNative.Platform.select({
514
- ios: {
515
- shadowColor: "#000",
516
- shadowOpacity: 0.18,
517
- shadowRadius: 24,
518
- shadowOffset: { width: 0, height: -4 }
519
- },
520
- android: { elevation: 16 }
521
- })
522
- },
523
- handle: {
524
- alignSelf: "center",
525
- width: 44,
526
- height: 4,
527
- borderRadius: 2,
528
- backgroundColor: "rgba(15,23,42,0.12)",
529
- marginBottom: 12
530
- },
531
- headerRow: {
532
- flexDirection: "row",
533
- alignItems: "center",
534
- justifyContent: "space-between",
535
- marginBottom: 12
536
- },
537
- title: {
538
- fontSize: 16,
539
- fontWeight: "600",
540
- color: "#1d1d1f"
541
- },
542
- closeBtn: {
543
- width: 30,
544
- height: 30,
545
- borderRadius: 15,
546
- alignItems: "center",
547
- justifyContent: "center",
548
- backgroundColor: "rgba(15,23,42,0.04)"
549
- },
550
- options: { gap: 8 },
551
- option: {
552
- flexDirection: "row",
553
- alignItems: "center",
554
- gap: 12,
555
- paddingHorizontal: 12,
556
- paddingVertical: 14,
557
- borderRadius: 14,
558
- backgroundColor: "#f8fafc",
559
- borderWidth: reactNative.StyleSheet.hairlineWidth,
560
- borderColor: "rgba(15,23,42,0.06)"
561
- },
562
- optionIconWrap: {
563
- width: 38,
564
- height: 38,
565
- borderRadius: 19,
566
- backgroundColor: "rgba(0,133,141,0.10)",
567
- alignItems: "center",
568
- justifyContent: "center"
569
- },
570
- optionTitle: {
571
- fontSize: 15,
572
- fontWeight: "600",
573
- color: "#1d1d1f"
574
- },
575
- optionSubtitle: {
576
- fontSize: 12,
577
- color: "#6b6b67",
578
- marginTop: 2
579
- }
580
- });
581
- var DEFAULT_MAX_LENGTH = 6;
582
- var MAX_SUPPORTED_LENGTH = 12;
583
- var OTP_ERROR_BORDER = "#ef4444";
584
- var COMPLETE_PULSE_MS = 350;
585
- var SHAKE_MS = 400;
586
- function OtpInput({
587
- value,
588
- onChange,
589
- maxLength,
590
- disabled = false,
591
- error = false
592
- }) {
593
- const inputRefs = React.useRef([]);
594
- const shakeAnim = React.useRef(new reactNative.Animated.Value(0)).current;
595
- const scaleAnim = React.useRef(new reactNative.Animated.Value(1)).current;
596
- const prevLenRef = React.useRef(0);
597
- const [internalError, setInternalError] = React.useState(false);
598
- const safeMaxLength = Number.isInteger(maxLength) && maxLength > 0 ? Math.min(maxLength, MAX_SUPPORTED_LENGTH) : DEFAULT_MAX_LENGTH;
599
- const digits = value.split("").concat(Array(safeMaxLength).fill("")).slice(0, safeMaxLength);
600
- const isFull = value.length === safeMaxLength && /^\d+$/.test(value);
601
- React.useEffect(() => {
602
- if (!disabled) {
603
- inputRefs.current[0]?.focus();
604
- }
605
- }, [disabled]);
606
- React.useEffect(() => {
607
- if (!error) {
608
- setInternalError(false);
609
- return;
610
- }
611
- setInternalError(true);
612
- reactNative.Animated.sequence([
613
- reactNative.Animated.timing(shakeAnim, { toValue: 1, duration: SHAKE_MS, useNativeDriver: true }),
614
- reactNative.Animated.timing(shakeAnim, { toValue: 0, duration: 0, useNativeDriver: true })
615
- ]).start();
616
- }, [error, shakeAnim]);
617
- React.useEffect(() => {
618
- if (isFull && prevLenRef.current < safeMaxLength) {
619
- reactNative.Animated.sequence([
620
- reactNative.Animated.timing(scaleAnim, { toValue: 1.04, duration: COMPLETE_PULSE_MS * 0.4, useNativeDriver: true }),
621
- reactNative.Animated.timing(scaleAnim, { toValue: 1, duration: COMPLETE_PULSE_MS * 0.6, useNativeDriver: true })
622
- ]).start();
623
- }
624
- prevLenRef.current = value.length;
625
- }, [isFull, value.length, safeMaxLength, scaleAnim]);
626
- const shakeTranslate = shakeAnim.interpolate({
627
- inputRange: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 0.9, 1],
628
- outputRange: [0, -6, 5, -4, 3, -2, 1, 0]
629
- });
630
- const focusInput = (index) => {
631
- if (index >= 0 && index < safeMaxLength) {
632
- inputRefs.current[index]?.focus();
633
- }
634
- };
635
- const updateValue = (newDigits) => {
636
- onChange(newDigits.join("").slice(0, safeMaxLength));
637
- };
638
- const handleChange = (index, text) => {
639
- const cleaned = text.replace(/\D/g, "");
640
- if (cleaned.length > 1) {
641
- const pasted = cleaned.slice(0, safeMaxLength);
642
- const newDigits2 = pasted.split("").concat(Array(safeMaxLength).fill("")).slice(0, safeMaxLength);
643
- updateValue(newDigits2);
644
- focusInput(Math.min(pasted.length, safeMaxLength - 1));
645
- return;
646
- }
647
- const char = cleaned.slice(-1);
648
- const newDigits = [...digits];
649
- newDigits[index] = char;
650
- updateValue(newDigits);
651
- if (char && index < safeMaxLength - 1) {
652
- focusInput(index + 1);
653
- }
654
- };
655
- const handleKeyPress = (index, key) => {
656
- if (key === "Backspace") {
657
- if (digits[index]) {
658
- const newDigits = [...digits];
659
- newDigits[index] = "";
660
- updateValue(newDigits);
661
- } else if (index > 0) {
662
- const newDigits = [...digits];
663
- newDigits[index - 1] = "";
664
- updateValue(newDigits);
665
- focusInput(index - 1);
666
- }
667
- }
668
- };
669
- return /* @__PURE__ */ jsxRuntime.jsx(
670
- reactNative.Animated.View,
671
- {
672
- style: [
673
- s2.container,
674
- { transform: [{ translateX: shakeTranslate }, { scale: scaleAnim }] }
675
- ],
676
- children: digits.map((digit, i) => /* @__PURE__ */ jsxRuntime.jsx(
677
- reactNative.TextInput,
678
- {
679
- ref: (el) => {
680
- inputRefs.current[i] = el;
681
- },
682
- value: digit,
683
- editable: !disabled,
684
- keyboardType: "number-pad",
685
- maxLength: 1,
686
- onChangeText: (text) => handleChange(i, text),
687
- onKeyPress: ({ nativeEvent }) => handleKeyPress(i, nativeEvent.key),
688
- onFocus: () => inputRefs.current[i]?.setNativeProps({ selection: { start: 0, end: 1 } }),
689
- style: [
690
- s2.input,
691
- disabled && s2.inputDisabled,
692
- internalError && s2.inputError
693
- ],
694
- accessibilityLabel: `Digit ${i + 1}`
695
- },
696
- i
697
- ))
698
- }
699
- );
700
- }
701
- var PAYMAN_OTP = {
702
- bg: "#FFFFFF",
703
- border: "rgba(0,0,0,0.1)",
704
- fg: "#18181b",
705
- disabledBg: "rgba(0,0,0,0.03)"
706
- };
707
- var s2 = reactNative.StyleSheet.create({
708
- container: { flexDirection: "row", gap: 8, justifyContent: "center" },
709
- input: {
710
- width: 44,
711
- height: 50,
712
- textAlign: "center",
713
- fontSize: 20,
714
- fontWeight: "600",
715
- borderWidth: 1,
716
- borderColor: PAYMAN_OTP.border,
717
- borderRadius: 10,
718
- color: PAYMAN_OTP.fg,
719
- backgroundColor: PAYMAN_OTP.bg
720
- },
721
- inputDisabled: { backgroundColor: PAYMAN_OTP.disabledBg, opacity: 0.5 },
722
- inputError: {
723
- borderColor: OTP_ERROR_BORDER,
724
- borderWidth: 1.5
725
- }
726
- });
727
-
728
- // src/components/UserActionModal/constants.ts
729
- var BUTTON_LABELS = {
730
- /** Link-style actions (new layout) */
731
- RESEND_CODE: "Resend OTP",
732
- CANCEL_TRANSFER: "Cancel Payment",
733
- /** Short cancel label for payee approval flows */
734
- CANCEL: "Cancel"
735
- };
736
- var RESEND_OTP_COOLDOWN_SECONDS = 30;
737
- var DEFAULT_OTP_MAX_LENGTH = 6;
738
- var MIN_OTP_MAX_LENGTH = 1;
739
- var MAX_OTP_MAX_LENGTH = 12;
740
- var ACTION_PENDING_TIMEOUT_MS = 15e3;
741
- var MODAL_CONTENT = {
742
- LOADING_APPROVE: "Verifying...",
743
- LOADING_REJECT: "Rejecting...",
744
- LOADING_RESEND: "Resending...",
745
- RESEND_AVAILABLE_IN: "Resend OTP in",
746
- SECURED_BY_PREFIX: "Secured by",
747
- SECURED_BY_BRAND: "Payman"
748
- };
749
-
750
- // src/components/UserActionModal/utils.ts
751
- function getOtpSchemaFromRequest(schema) {
752
- const properties = schema?.properties;
753
- const otp = properties?.otp;
754
- const maxLengthRaw = otp?.maxLength;
755
- const parsedMaxLength = Number.isInteger(maxLengthRaw) ? Number(maxLengthRaw) : DEFAULT_OTP_MAX_LENGTH;
756
- const clampedMaxLength = Math.min(
757
- MAX_OTP_MAX_LENGTH,
758
- Math.max(MIN_OTP_MAX_LENGTH, parsedMaxLength)
759
- );
760
- return {
761
- maxLength: clampedMaxLength
762
- };
763
- }
764
- function formatAmountForDisplay(amount) {
765
- const normalized = amount.replace(/,/g, "").trim();
766
- const n = Number(normalized);
767
- if (!Number.isFinite(n)) {
768
- return amount.startsWith("$") ? amount : `$${amount}`;
769
- }
770
- return new Intl.NumberFormat("en-US", {
771
- style: "currency",
772
- currency: "USD"
773
- }).format(n);
774
- }
775
- var BRAND = "#15687d";
776
- var TEXT_PRIMARY = "#111827";
777
- var TEXT_SECONDARY = "#64748b";
778
- var BORDER = "rgba(15,23,42,0.10)";
779
- var OTP_ERROR_FLASH_MS = 600;
780
- function InlineVerificationPanel({
781
- userActionRequest,
782
- clearOtpTrigger,
783
- onApprove,
784
- onReject,
785
- onResend
786
- }) {
787
- const [otp, setOtp] = React.useState("");
788
- const [actionType, setActionType] = React.useState(null);
789
- const [isSubmitting, setIsSubmitting] = React.useState(false);
790
- const [resendCooldownRemaining, setResendCooldownRemaining] = React.useState(0);
791
- const [otpError, setOtpError] = React.useState(false);
792
- const entrance = React.useRef(new reactNative.Animated.Value(0)).current;
793
- const lastAutoSubmittedRef = React.useRef("");
794
- const submitInFlightRef = React.useRef(false);
795
- const submitGenerationRef = React.useRef(0);
796
- const schema = getOtpSchemaFromRequest(userActionRequest?.requestedSchema);
797
- const resetActionState = React.useCallback(() => {
798
- setIsSubmitting(false);
799
- setActionType(null);
800
- }, []);
801
- React.useEffect(() => {
802
- if (userActionRequest) {
803
- setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
804
- entrance.setValue(0);
805
- reactNative.Animated.timing(entrance, {
806
- toValue: 1,
807
- duration: 220,
808
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
809
- useNativeDriver: true
810
- }).start();
811
- } else {
812
- setOtp("");
813
- resetActionState();
814
- setResendCooldownRemaining(0);
815
- setOtpError(false);
816
- lastAutoSubmittedRef.current = "";
817
- submitInFlightRef.current = false;
818
- submitGenerationRef.current += 1;
819
- }
820
- }, [entrance, resetActionState, userActionRequest]);
821
- React.useEffect(() => {
822
- if (resendCooldownRemaining <= 0) return;
823
- const timer = setTimeout(
824
- () => setResendCooldownRemaining((prev) => prev - 1),
825
- 1e3
826
- );
827
- return () => clearTimeout(timer);
828
- }, [resendCooldownRemaining]);
829
- React.useEffect(() => {
830
- if (clearOtpTrigger <= 0) return;
831
- setOtpError(true);
832
- const timer = setTimeout(() => {
833
- setOtpError(false);
834
- setOtp("");
835
- resetActionState();
836
- }, OTP_ERROR_FLASH_MS);
837
- return () => clearTimeout(timer);
838
- }, [clearOtpTrigger, resetActionState]);
839
- React.useEffect(() => {
840
- if (!userActionRequest || !isSubmitting) return;
841
- if (actionType !== "approve" && actionType !== "reject") return;
842
- const timeout = setTimeout(
843
- () => resetActionState(),
844
- ACTION_PENDING_TIMEOUT_MS
845
- );
846
- return () => clearTimeout(timeout);
847
- }, [actionType, isSubmitting, resetActionState, userActionRequest]);
848
- React.useEffect(() => {
849
- if (!userActionRequest) return;
850
- if (otp.length !== schema.maxLength || !/^\d+$/.test(otp)) return;
851
- if (isSubmitting || submitInFlightRef.current) return;
852
- if (lastAutoSubmittedRef.current === otp) return;
853
- lastAutoSubmittedRef.current = otp;
854
- submitInFlightRef.current = true;
855
- const submitGeneration = submitGenerationRef.current;
856
- void (async () => {
857
- setIsSubmitting(true);
858
- setActionType("approve");
859
- try {
860
- await onApprove(otp);
861
- } catch {
862
- if (submitGenerationRef.current !== submitGeneration) return;
863
- resetActionState();
864
- lastAutoSubmittedRef.current = otp;
865
- } finally {
866
- if (submitGenerationRef.current !== submitGeneration) return;
867
- submitInFlightRef.current = false;
868
- }
869
- })();
870
- }, [
871
- isSubmitting,
872
- onApprove,
873
- otp,
874
- resetActionState,
875
- schema.maxLength,
876
- userActionRequest
877
- ]);
878
- const handleReject = React.useCallback(async () => {
879
- setIsSubmitting(true);
880
- setActionType("reject");
881
- try {
882
- await onReject();
883
- } catch {
884
- resetActionState();
885
- }
886
- }, [onReject, resetActionState]);
887
- const handleResend = React.useCallback(async () => {
888
- if (resendCooldownRemaining > 0) return;
889
- setIsSubmitting(true);
890
- setActionType("resend");
891
- try {
892
- await onResend();
893
- setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
894
- } catch {
895
- } finally {
896
- setActionType(null);
897
- setIsSubmitting(false);
898
- }
899
- }, [onResend, resendCooldownRemaining]);
900
- if (!userActionRequest) return null;
901
- const translateY = entrance.interpolate({
902
- inputRange: [0, 1],
903
- outputRange: [10, 0]
904
- });
905
- const isVerifying = actionType === "approve" && isSubmitting;
906
- const isCancelling = actionType === "reject" && isSubmitting;
907
- return /* @__PURE__ */ jsxRuntime.jsxs(
908
- reactNative.Animated.View,
909
- {
910
- style: [
911
- s3.panel,
912
- { opacity: entrance, transform: [{ translateY }] }
913
- ],
914
- accessibilityLabel: "payman-inline-verification",
915
- children: [
916
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s3.title, children: "Enter the verification code sent to your email" }),
917
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s3.otpWrap, accessibilityLabel: "payman-otp-wrap", children: /* @__PURE__ */ jsxRuntime.jsx(
918
- OtpInput,
919
- {
920
- value: otp,
921
- onChange: setOtp,
922
- maxLength: schema.maxLength,
923
- disabled: isSubmitting,
924
- error: otpError
925
- }
926
- ) }),
927
- isVerifying ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s3.statusRow, children: [
928
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { size: "small", color: BRAND }),
929
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s3.statusText, children: MODAL_CONTENT.LOADING_APPROVE })
930
- ] }) : null,
931
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s3.actions, children: [
932
- /* @__PURE__ */ jsxRuntime.jsx(
933
- reactNative.Pressable,
934
- {
935
- onPress: handleResend,
936
- disabled: isSubmitting || resendCooldownRemaining > 0,
937
- hitSlop: 8,
938
- accessibilityLabel: "payman-inline-verification-resend",
939
- children: /* @__PURE__ */ jsxRuntime.jsx(
940
- reactNative.Text,
941
- {
942
- style: [
943
- s3.actionText,
944
- (isSubmitting || resendCooldownRemaining > 0) && s3.actionTextDisabled
945
- ],
946
- children: actionType === "resend" ? MODAL_CONTENT.LOADING_RESEND : resendCooldownRemaining > 0 ? `${MODAL_CONTENT.RESEND_AVAILABLE_IN} ${resendCooldownRemaining}s` : BUTTON_LABELS.RESEND_CODE
947
- }
948
- )
949
- }
950
- ),
951
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s3.divider }),
952
- /* @__PURE__ */ jsxRuntime.jsx(
953
- reactNative.Pressable,
954
- {
955
- onPress: handleReject,
956
- disabled: isSubmitting,
957
- hitSlop: 8,
958
- accessibilityLabel: "payman-inline-verification-cancel",
959
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s3.cancelContent, children: [
960
- isCancelling ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { size: "small", color: TEXT_SECONDARY }) : null,
961
- /* @__PURE__ */ jsxRuntime.jsx(
962
- reactNative.Text,
963
- {
964
- style: [
965
- s3.actionText,
966
- isSubmitting && !isCancelling && s3.actionTextDisabled
967
- ],
968
- children: isCancelling ? MODAL_CONTENT.LOADING_REJECT : BUTTON_LABELS.CANCEL
969
- }
970
- )
971
- ] })
972
- }
973
- )
974
- ] })
975
- ]
976
- }
977
- );
978
- }
979
- var s3 = reactNative.StyleSheet.create({
980
- panel: {
981
- backgroundColor: "#f8fafc",
982
- borderWidth: 1,
983
- borderBottomWidth: 0,
984
- borderColor: BORDER,
985
- borderTopLeftRadius: 24,
986
- borderTopRightRadius: 24,
987
- paddingHorizontal: 14,
988
- paddingTop: 14,
989
- paddingBottom: 12
990
- },
991
- title: {
992
- color: TEXT_PRIMARY,
993
- fontSize: 14,
994
- fontWeight: "600",
995
- lineHeight: 20,
996
- textAlign: "center",
997
- marginBottom: 12
998
- },
999
- otpWrap: {
1000
- alignItems: "center"
1001
- },
1002
- statusRow: {
1003
- flexDirection: "row",
1004
- alignItems: "center",
1005
- justifyContent: "center",
1006
- gap: 8,
1007
- marginTop: 10
1008
- },
1009
- statusText: {
1010
- color: TEXT_SECONDARY,
1011
- fontSize: 12,
1012
- fontWeight: "500"
1013
- },
1014
- actions: {
1015
- flexDirection: "row",
1016
- alignItems: "center",
1017
- justifyContent: "center",
1018
- gap: 12,
1019
- marginTop: 12
1020
- },
1021
- actionText: {
1022
- color: BRAND,
1023
- fontSize: 12,
1024
- fontWeight: "600"
1025
- },
1026
- actionTextDisabled: {
1027
- color: TEXT_SECONDARY,
1028
- opacity: 0.55
1029
- },
1030
- divider: {
1031
- width: 1,
1032
- height: 14,
1033
- backgroundColor: BORDER
1034
- },
1035
- cancelContent: {
1036
- flexDirection: "row",
1037
- alignItems: "center",
1038
- gap: 6
1039
- }
1040
- });
1041
-
1042
- // src/utils/errorMessages.ts
1043
- var WORKFLOW_FAILED = "WORKFLOW_FAILED";
1044
- var STREAM_NOT_STARTED = "STREAM_NOT_STARTED";
1045
- var HTTP_ERROR_PREFIX = /^HTTP\s+(\d+)\s*:\s*([\s\S]+)$/;
1046
- function isFriendlyWorkflowError(errorDetails) {
1047
- if (!errorDetails) return false;
1048
- return errorDetails === WORKFLOW_FAILED || errorDetails === STREAM_NOT_STARTED || errorDetails.includes(WORKFLOW_FAILED);
1049
- }
1050
- function parseErrorPayload(payload) {
1051
- try {
1052
- const parsed = JSON.parse(payload);
1053
- if (typeof parsed === "string") {
1054
- return { message: parsed.trim() || void 0 };
1055
- }
1056
- if (typeof parsed === "object" && parsed !== null) {
1057
- const record = parsed;
1058
- return {
1059
- status: typeof record.status === "number" ? record.status : void 0,
1060
- message: typeof record.message === "string" && record.message.trim() ? record.message.trim() : void 0
1061
- };
1062
- }
1063
- } catch {
1064
- }
1065
- return {};
1066
- }
1067
- function getConflictErrorMessage(errorDetails) {
1068
- if (!errorDetails) return void 0;
1069
- const trimmedError = errorDetails.trim();
1070
- const httpMatch = trimmedError.match(HTTP_ERROR_PREFIX);
1071
- const httpStatus = httpMatch ? Number(httpMatch[1]) : void 0;
1072
- const rawPayload = (httpMatch ? httpMatch[2] : trimmedError).trim();
1073
- const payload = parseErrorPayload(rawPayload);
1074
- const status = payload.status ?? httpStatus;
1075
- if (status !== 409) {
1076
- return void 0;
1077
- }
1078
- if (payload.message) {
1079
- return payload.message;
1080
- }
1081
- return rawPayload || void 0;
1082
- }
1083
- var TEAL = "#00858d";
1084
- function UserMessageV2({
1085
- message,
1086
- actions,
1087
- onEdit,
1088
- onRetry,
1089
- retryDisabled = false
1090
- }) {
1091
- const [copied, setCopied] = React.useState(false);
1092
- const timerRef = React.useRef(null);
1093
- const showCopyAction = actions?.copy ?? true;
1094
- const showEditAction = actions?.edit ?? false;
1095
- const showRetryAction = actions?.retry ?? false;
1096
- const hasVisibleActions = showCopyAction || showEditAction && !!onEdit || showRetryAction && !!onRetry;
1097
- const fadeIn = React.useRef(new reactNative.Animated.Value(0)).current;
1098
- const slideIn = React.useRef(new reactNative.Animated.Value(8)).current;
1099
- React.useEffect(() => {
1100
- reactNative.Animated.parallel([
1101
- reactNative.Animated.timing(fadeIn, {
1102
- toValue: 1,
1103
- duration: 200,
1104
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
1105
- useNativeDriver: true
1106
- }),
1107
- reactNative.Animated.timing(slideIn, {
1108
- toValue: 0,
1109
- duration: 200,
1110
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
1111
- useNativeDriver: true
1112
- })
1113
- ]).start();
1114
- }, [fadeIn, slideIn]);
1115
- const handleCopy = React.useCallback(() => {
1116
- reactNative.Clipboard.setString(message.content);
1117
- setCopied(true);
1118
- if (timerRef.current) clearTimeout(timerRef.current);
1119
- timerRef.current = setTimeout(() => setCopied(false), 1800);
1120
- }, [message.content]);
1121
- React.useEffect(() => {
1122
- return () => {
1123
- if (timerRef.current) clearTimeout(timerRef.current);
1124
- };
1125
- }, []);
1126
- const conflictErr = message.errorDetails ? getConflictErrorMessage(message.errorDetails) ?? message.errorDetails : null;
1127
- return /* @__PURE__ */ jsxRuntime.jsxs(
1128
- reactNative.Animated.View,
1129
- {
1130
- style: [s4.wrapper, { opacity: fadeIn, transform: [{ translateY: slideIn }] }],
1131
- children: [
1132
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s4.bubble, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { selectable: true, style: s4.text, children: message.content }) }),
1133
- message.isError && conflictErr ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s4.errorRow, children: [
1134
- /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.AlertCircle, { size: 13, color: "rgba(239, 68, 68, 0.8)", strokeWidth: 2 }),
1135
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s4.errorText, children: conflictErr })
1136
- ] }) : null,
1137
- hasVisibleActions ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s4.actions, children: [
1138
- showCopyAction ? /* @__PURE__ */ jsxRuntime.jsx(
1139
- reactNative.Pressable,
1140
- {
1141
- onPress: handleCopy,
1142
- hitSlop: 8,
1143
- style: s4.actionBtn,
1144
- accessibilityLabel: "Copy message",
1145
- children: copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Check, { size: 13, color: "#059669", strokeWidth: 2.5 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Copy, { size: 13, color: "#94a3b8", strokeWidth: 2 })
1146
- }
1147
- ) : null,
1148
- showEditAction && onEdit ? /* @__PURE__ */ jsxRuntime.jsx(
1149
- reactNative.Pressable,
1150
- {
1151
- onPress: () => onEdit(message.id),
1152
- hitSlop: 8,
1153
- style: s4.actionBtn,
1154
- accessibilityLabel: "Edit message",
1155
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Pencil, { size: 13, color: "#94a3b8", strokeWidth: 2 })
1156
- }
1157
- ) : null,
1158
- showRetryAction && onRetry ? /* @__PURE__ */ jsxRuntime.jsx(
1159
- reactNative.Pressable,
1160
- {
1161
- onPress: () => onRetry(message.id),
1162
- disabled: retryDisabled,
1163
- hitSlop: 8,
1164
- style: [s4.actionBtn, retryDisabled && s4.actionBtnDisabled],
1165
- accessibilityLabel: "Retry message",
1166
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.RotateCcw, { size: 13, color: "#94a3b8", strokeWidth: 2 })
1167
- }
1168
- ) : null
1169
- ] }) : null
1170
- ]
1171
- }
1172
- );
1173
- }
1174
- var s4 = reactNative.StyleSheet.create({
1175
- wrapper: {
1176
- paddingHorizontal: 16,
1177
- paddingVertical: 5,
1178
- alignItems: "flex-end"
1179
- },
1180
- bubble: {
1181
- backgroundColor: TEAL,
1182
- borderRadius: 20,
1183
- borderBottomRightRadius: 6,
1184
- paddingHorizontal: 14,
1185
- paddingVertical: 10,
1186
- maxWidth: 320
1187
- },
1188
- text: {
1189
- fontSize: 15,
1190
- lineHeight: 22,
1191
- color: "#fff"
1192
- },
1193
- errorRow: {
1194
- flexDirection: "row",
1195
- alignItems: "flex-start",
1196
- gap: 6,
1197
- marginTop: 4,
1198
- paddingRight: 4,
1199
- maxWidth: 320
1200
- },
1201
- errorText: {
1202
- flex: 1,
1203
- fontSize: 12,
1204
- color: "rgba(239, 68, 68, 0.95)",
1205
- lineHeight: 18
1206
- },
1207
- actions: {
1208
- flexDirection: "row",
1209
- marginTop: 6,
1210
- gap: 4
1211
- },
1212
- actionBtn: {
1213
- padding: 6,
1214
- borderRadius: 8
1215
- },
1216
- actionBtnDisabled: {
1217
- opacity: 0.45
1218
- }
1219
- });
1220
- var THINKING_SPEED = {
1221
- normal: [6, 8],
1222
- fast: 1,
1223
- punctuation: [20, 30],
1224
- newline: [12, 18],
1225
- idle: 30
1226
- };
1227
- var RESPONSE_SPEED = {
1228
- normal: [4, 8],
1229
- fast: 1,
1230
- punctuation: [20, 30],
1231
- newline: [10, 15],
1232
- idle: 30
1233
- };
1234
- function charDelay(char, speed) {
1235
- if (char === "*") return speed.fast;
1236
- if (char === "\n") return speed.newline[0] + Math.random() * speed.newline[1];
1237
- if (".!?,;:".includes(char))
1238
- return speed.punctuation[0] + Math.random() * speed.punctuation[1];
1239
- return speed.normal[0] + Math.random() * speed.normal[1];
1240
- }
1241
- var MARKDOWN_IMAGE_REGEX = /^!\[[^\]]*\]\([^)]*\)/;
1242
- var typingProgressCache = /* @__PURE__ */ new Map();
1243
- function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDisplayedText, cacheKey) {
1244
- const cached = cacheKey ? typingProgressCache.get(cacheKey) : void 0;
1245
- const hydratedFromCache = cached !== void 0 && targetText.startsWith(cached) ? cached : void 0;
1246
- const [displayedText, setDisplayedText] = React.useState(hydratedFromCache ?? "");
1247
- const displayedRef = React.useRef(hydratedFromCache ?? "");
1248
- const targetRef = React.useRef(targetText);
1249
- const enabledRef = React.useRef(enabled);
1250
- const initialDisplayedRef = React.useRef(initialDisplayedText);
1251
- const cacheKeyRef = React.useRef(cacheKey);
1252
- const timerRef = React.useRef(null);
1253
- const runningRef = React.useRef(false);
1254
- targetRef.current = targetText;
1255
- enabledRef.current = enabled;
1256
- initialDisplayedRef.current = initialDisplayedText;
1257
- cacheKeyRef.current = cacheKey;
1258
- const writeDisplayed = (next) => {
1259
- displayedRef.current = next;
1260
- setDisplayedText(next);
1261
- if (cacheKeyRef.current) {
1262
- typingProgressCache.set(cacheKeyRef.current, next);
1263
- }
1264
- };
1265
- React.useEffect(() => {
1266
- if (!enabled) {
1267
- if (timerRef.current) {
1268
- clearTimeout(timerRef.current);
1269
- timerRef.current = null;
1270
- }
1271
- runningRef.current = false;
1272
- displayedRef.current = targetText;
1273
- setDisplayedText(targetText);
1274
- if (cacheKeyRef.current) {
1275
- typingProgressCache.delete(cacheKeyRef.current);
1276
- }
1277
- return;
1278
- }
1279
- if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
1280
- displayedRef.current = "";
1281
- setDisplayedText("");
1282
- if (cacheKeyRef.current) typingProgressCache.delete(cacheKeyRef.current);
1283
- }
1284
- if (displayedRef.current.length === 0 && initialDisplayedRef.current && targetRef.current.startsWith(initialDisplayedRef.current)) {
1285
- writeDisplayed(initialDisplayedRef.current);
1286
- }
1287
- if (runningRef.current) return;
1288
- runningRef.current = true;
1289
- const tick = () => {
1290
- if (!enabledRef.current) {
1291
- runningRef.current = false;
1292
- return;
1293
- }
1294
- if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
1295
- let divergeAt = 0;
1296
- const minLen = Math.min(displayedRef.current.length, targetRef.current.length);
1297
- while (divergeAt < minLen && displayedRef.current[divergeAt] === targetRef.current[divergeAt]) {
1298
- divergeAt++;
1299
- }
1300
- const imgStart = targetRef.current.slice(0, divergeAt).lastIndexOf("![");
1301
- if (imgStart >= 0) {
1302
- const newImgMatch = MARKDOWN_IMAGE_REGEX.exec(targetRef.current.slice(imgStart));
1303
- const oldImgMatch = MARKDOWN_IMAGE_REGEX.exec(displayedRef.current.slice(imgStart));
1304
- if (newImgMatch && oldImgMatch) {
1305
- const oldImgEnd = imgStart + oldImgMatch[0].length;
1306
- const newImgEnd = imgStart + newImgMatch[0].length;
1307
- const newCursor = Math.min(
1308
- Math.max(displayedRef.current.length + (newImgEnd - oldImgEnd), newImgEnd),
1309
- targetRef.current.length
1310
- );
1311
- writeDisplayed(targetRef.current.slice(0, newCursor));
1312
- timerRef.current = setTimeout(tick, 0);
1313
- return;
1314
- }
1315
- }
1316
- writeDisplayed(targetRef.current.slice(0, imgStart >= 0 ? imgStart : divergeAt));
1317
- timerRef.current = setTimeout(tick, 0);
1318
- return;
1319
- }
1320
- if (displayedRef.current.length < targetRef.current.length) {
1321
- const remaining = targetRef.current.slice(displayedRef.current.length);
1322
- const imgMatch = MARKDOWN_IMAGE_REGEX.exec(remaining);
1323
- if (imgMatch) {
1324
- writeDisplayed(displayedRef.current + imgMatch[0]);
1325
- timerRef.current = setTimeout(tick, 0);
1326
- return;
1327
- }
1328
- const nextChar = remaining[0];
1329
- writeDisplayed(displayedRef.current + nextChar);
1330
- const delay = charDelay(nextChar, speed);
1331
- timerRef.current = setTimeout(tick, delay);
1332
- } else {
1333
- timerRef.current = setTimeout(tick, speed.idle);
1334
- }
1335
- };
1336
- tick();
1337
- return () => {
1338
- if (timerRef.current) {
1339
- clearTimeout(timerRef.current);
1340
- timerRef.current = null;
1341
- }
1342
- runningRef.current = false;
1343
- };
1344
- }, [enabled]);
1345
- const isTyping = enabled && displayedRef.current.length < targetRef.current.length;
1346
- return {
1347
- displayedText: enabled ? displayedText : targetText,
1348
- isTyping
1349
- };
1350
- }
1351
- var CURSOR_MESSAGES = [
1352
- "Analyzing",
1353
- "Processing",
1354
- "Calculating",
1355
- "Reviewing",
1356
- "Verifying",
1357
- "Assessing",
1358
- "Evaluating",
1359
- "Checking",
1360
- "Planning",
1361
- "Working",
1362
- "Updating",
1363
- "Validating",
1364
- "Monitoring",
1365
- "Optimizing",
1366
- "Reconciling",
1367
- "Forecasting",
1368
- "Inspecting",
1369
- "Organizing",
1370
- "Sorting",
1371
- "Scanning",
1372
- "Balancing",
1373
- "Summarizing",
1374
- "Predicting",
1375
- "Comparing",
1376
- "Tracking",
1377
- "Adjusting",
1378
- "Examining",
1379
- "Mapping",
1380
- "Modeling",
1381
- "Reporting",
1382
- "Confirming"
1383
- ];
1384
- var FINAL_CURSOR_MESSAGE = "Finishing up";
1385
- var INITIAL_THINKING_PLACEHOLDER = [
1386
- "**Getting things ready**",
1387
- "Putting things together"
1388
- ].join("\n");
1389
- var PAYMAN_TEAL2 = "#00858d";
1390
- var TEXT_PRIMARY2 = "#0f172a";
1391
- var TEXT_SECONDARY2 = "#475569";
1392
- var TEXT_TERTIARY = "#94a3b8";
1393
- var TEXT_MUTED = "#cbd5e1";
1394
- if (reactNative.Platform.OS === "android" && reactNative.UIManager.setLayoutAnimationEnabledExperimental) {
1395
- reactNative.UIManager.setLayoutAnimationEnabledExperimental(true);
1396
- }
1397
- function PaymanMarkNative({ size = 14 }) {
1398
- const scale = React.useRef(new reactNative.Animated.Value(0.7)).current;
1399
- const opacity = React.useRef(new reactNative.Animated.Value(0.6)).current;
1400
- React.useEffect(() => {
1401
- const loop = reactNative.Animated.loop(
1402
- reactNative.Animated.parallel([
1403
- reactNative.Animated.sequence([
1404
- reactNative.Animated.timing(scale, {
1405
- toValue: 1.6,
1406
- duration: 1100,
1407
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
1408
- useNativeDriver: true
1409
- }),
1410
- reactNative.Animated.timing(scale, {
1411
- toValue: 0.7,
1412
- duration: 0,
1413
- useNativeDriver: true
1414
- })
1415
- ]),
1416
- reactNative.Animated.sequence([
1417
- reactNative.Animated.timing(opacity, {
1418
- toValue: 0,
1419
- duration: 1100,
1420
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
1421
- useNativeDriver: true
1422
- }),
1423
- reactNative.Animated.timing(opacity, {
1424
- toValue: 0.55,
1425
- duration: 0,
1426
- useNativeDriver: true
1427
- })
1428
- ])
1429
- ])
1430
- );
1431
- loop.start();
1432
- return () => loop.stop();
1433
- }, [scale, opacity]);
1434
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [mark.wrap, { width: size, height: size }], children: [
1435
- /* @__PURE__ */ jsxRuntime.jsx(
1436
- reactNative.Animated.View,
1437
- {
1438
- style: [
1439
- mark.ring,
1440
- {
1441
- width: size,
1442
- height: size,
1443
- borderRadius: size / 2,
1444
- transform: [{ scale }],
1445
- opacity
1446
- }
1447
- ]
1448
- }
1449
- ),
1450
- /* @__PURE__ */ jsxRuntime.jsx(
1451
- reactNative.View,
1452
- {
1453
- style: [
1454
- mark.core,
1455
- {
1456
- width: size * 0.55,
1457
- height: size * 0.55,
1458
- borderRadius: size * 0.55
1459
- }
1460
- ]
1461
- }
1462
- )
1463
- ] });
1464
- }
1465
- function ShimmerText({
1466
- children,
1467
- style
1468
- }) {
1469
- const opacity = React.useRef(new reactNative.Animated.Value(0.5)).current;
1470
- React.useEffect(() => {
1471
- const anim = reactNative.Animated.loop(
1472
- reactNative.Animated.sequence([
1473
- reactNative.Animated.timing(opacity, {
1474
- toValue: 1,
1475
- duration: 900,
1476
- easing: reactNative.Easing.inOut(reactNative.Easing.ease),
1477
- useNativeDriver: true
1478
- }),
1479
- reactNative.Animated.timing(opacity, {
1480
- toValue: 0.5,
1481
- duration: 900,
1482
- easing: reactNative.Easing.inOut(reactNative.Easing.ease),
1483
- useNativeDriver: true
1484
- })
1485
- ])
1486
- );
1487
- anim.start();
1488
- return () => anim.stop();
1489
- }, [opacity]);
1490
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.Text, { style: [style, { opacity }], selectable: false, children });
1491
- }
1492
- function AnimatedSeconds({ value, style }) {
1493
- const prevRef = React.useRef(value);
1494
- const opacity = React.useRef(new reactNative.Animated.Value(1)).current;
1495
- const translate = React.useRef(new reactNative.Animated.Value(0)).current;
1496
- const [shown, setShown] = React.useState(value);
1497
- React.useEffect(() => {
1498
- if (value === prevRef.current) return;
1499
- prevRef.current = value;
1500
- reactNative.Animated.sequence([
1501
- reactNative.Animated.parallel([
1502
- reactNative.Animated.timing(opacity, { toValue: 0, duration: 90, useNativeDriver: true }),
1503
- reactNative.Animated.timing(translate, { toValue: -4, duration: 90, useNativeDriver: true })
1504
- ])
1505
- ]).start(() => {
1506
- setShown(value);
1507
- translate.setValue(4);
1508
- reactNative.Animated.parallel([
1509
- reactNative.Animated.timing(opacity, { toValue: 1, duration: 140, useNativeDriver: true }),
1510
- reactNative.Animated.timing(translate, { toValue: 0, duration: 140, useNativeDriver: true })
1511
- ]).start();
1512
- });
1513
- }, [value, opacity, translate]);
1514
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.Text, { style: [style, { opacity, transform: [{ translateY: translate }] }], selectable: false, children: formatDuration(shown) });
1515
- }
1516
- function formatDuration(seconds) {
1517
- if (seconds < 60) return `${seconds}s`;
1518
- const m = Math.floor(seconds / 60);
1519
- const s9 = seconds % 60;
1520
- return s9 > 0 ? `${m}m ${s9}s` : `${m}m`;
1521
- }
1522
- function parseThinking(content) {
1523
- const lines = content.split("\n");
1524
- const out = [];
1525
- for (const raw of lines) {
1526
- const line = raw.trim();
1527
- if (!line) continue;
1528
- const stmt = line.match(/^\*\*(.+)\*\*$/);
1529
- if (stmt) {
1530
- out.push({ type: "statement", text: stmt[1].trim() });
1531
- continue;
1532
- }
1533
- if (line.startsWith("\u2713 ")) {
1534
- out.push({ type: "status", variant: "success", text: line.slice(2).trim() });
1535
- continue;
1536
- }
1537
- if (line.startsWith("\u2717 ")) {
1538
- out.push({ type: "status", variant: "error", text: line.slice(2).trim() });
1539
- continue;
1540
- }
1541
- out.push({ type: "detail", text: line });
1542
- }
1543
- return out;
1544
- }
1545
- function ThinkingBlock({
1546
- content,
1547
- isStreaming,
1548
- durationSec,
1549
- startedAt
1550
- }) {
1551
- const [open, setOpen] = React.useState(true);
1552
- const [cursorIdx, setCursorIdx] = React.useState(0);
1553
- const [elapsedSec, setElapsedSec] = React.useState(
1554
- () => isStreaming && startedAt ? Math.max(0, Math.floor((Date.now() - startedAt) / 1e3)) : 0
1555
- );
1556
- const elapsedSecRef = React.useRef(elapsedSec);
1557
- const frozenSecRef = React.useRef(null);
1558
- const prevStreaming = React.useRef(isStreaming);
1559
- const chevronRot = React.useRef(new reactNative.Animated.Value(open ? 1 : 0)).current;
1560
- const parsed = React.useMemo(() => parseThinking(content), [content]);
1561
- const isFinalizingPhase = React.useMemo(() => {
1562
- const statements = parsed.filter(
1563
- (l) => l.type === "statement"
1564
- );
1565
- return statements.length > 0 && statements[statements.length - 1].text === "Finalizing";
1566
- }, [parsed]);
1567
- React.useEffect(() => {
1568
- if (prevStreaming.current && !isStreaming) {
1569
- reactNative.LayoutAnimation.configureNext({
1570
- duration: 220,
1571
- create: { type: "easeInEaseOut", property: "opacity" },
1572
- update: { type: "easeInEaseOut" },
1573
- delete: { type: "easeInEaseOut", property: "opacity" }
1574
- });
1575
- setOpen(false);
1576
- frozenSecRef.current = elapsedSecRef.current;
1577
- }
1578
- prevStreaming.current = isStreaming;
1579
- }, [isStreaming]);
1580
- React.useEffect(() => {
1581
- if (!isStreaming || !startedAt) return;
1582
- let active = true;
1583
- const tick = () => {
1584
- if (!active) return;
1585
- const val = Math.max(0, Math.floor((Date.now() - startedAt) / 1e3));
1586
- elapsedSecRef.current = val;
1587
- setElapsedSec(val);
1588
- };
1589
- tick();
1590
- const id = setInterval(tick, 1e3);
1591
- return () => {
1592
- active = false;
1593
- clearInterval(id);
1594
- };
1595
- }, [isStreaming, startedAt]);
1596
- React.useEffect(() => {
1597
- if (!isStreaming || isFinalizingPhase) return;
1598
- const id = setInterval(() => {
1599
- setCursorIdx((i) => (i + 1) % CURSOR_MESSAGES.length);
1600
- }, 4e3);
1601
- return () => clearInterval(id);
1602
- }, [isStreaming, isFinalizingPhase]);
1603
- React.useEffect(() => {
1604
- reactNative.Animated.timing(chevronRot, {
1605
- toValue: open ? 1 : 0,
1606
- duration: 180,
1607
- easing: reactNative.Easing.inOut(reactNative.Easing.ease),
1608
- useNativeDriver: true
1609
- }).start();
1610
- }, [open, chevronRot]);
1611
- const justStoppedSec = !isStreaming && prevStreaming.current && startedAt ? Math.max(0, Math.floor((Date.now() - startedAt) / 1e3)) : void 0;
1612
- const finalSec = (() => {
1613
- const candidates = [
1614
- frozenSecRef.current,
1615
- justStoppedSec,
1616
- frozenSecRef.current == null && !prevStreaming.current ? durationSec : void 0
1617
- ].filter((v) => typeof v === "number" && Number.isFinite(v));
1618
- return candidates.length ? Math.max(...candidates) : void 0;
1619
- })();
1620
- const cursorText = isFinalizingPhase ? FINAL_CURSOR_MESSAGE : CURSOR_MESSAGES[cursorIdx];
1621
- const toggle = React.useCallback(() => {
1622
- reactNative.LayoutAnimation.configureNext({
1623
- duration: 200,
1624
- create: { type: "easeInEaseOut", property: "opacity" },
1625
- update: { type: "easeInEaseOut" },
1626
- delete: { type: "easeInEaseOut", property: "opacity" }
1627
- });
1628
- setOpen((o) => !o);
1629
- }, []);
1630
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ts.container, children: [
1631
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { style: ts.header, onPress: toggle, hitSlop: 6, children: [
1632
- /* @__PURE__ */ jsxRuntime.jsx(
1633
- reactNative.Animated.View,
1634
- {
1635
- style: {
1636
- transform: [
1637
- {
1638
- rotate: chevronRot.interpolate({
1639
- inputRange: [0, 1],
1640
- outputRange: ["0deg", "90deg"]
1641
- })
1642
- }
1643
- ]
1644
- },
1645
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ChevronRight, { size: 14, color: TEXT_TERTIARY, strokeWidth: 2.25 })
1646
- }
1647
- ),
1648
- isStreaming ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ts.headerLabelRow, children: [
1649
- /* @__PURE__ */ jsxRuntime.jsx(ShimmerText, { style: ts.headerLabel, children: "Working on it\u2026" }),
1650
- startedAt !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(AnimatedSeconds, { value: elapsedSec, style: ts.timerText }) : null
1651
- ] }) : finalSec != null && finalSec > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ts.headerLabelRow, children: [
1652
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ts.headerLabelStatic, children: "Thought for " }),
1653
- /* @__PURE__ */ jsxRuntime.jsx(AnimatedSeconds, { value: finalSec, style: ts.headerLabelStatic })
1654
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ts.headerLabelStatic, children: "Thought" })
1655
- ] }),
1656
- open ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ts.body, children: [
1657
- parsed.map((item, i) => {
1658
- if (item.type === "statement") {
1659
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ts.statement, children: item.text }, i);
1660
- }
1661
- if (item.type === "status") {
1662
- return /* @__PURE__ */ jsxRuntime.jsx(
1663
- reactNative.View,
1664
- {
1665
- style: [
1666
- ts.statusBadge,
1667
- item.variant === "success" ? ts.statusSuccessBg : ts.statusErrorBg
1668
- ],
1669
- children: /* @__PURE__ */ jsxRuntime.jsx(
1670
- reactNative.Text,
1671
- {
1672
- style: [
1673
- ts.statusBadgeText,
1674
- item.variant === "success" ? ts.statusSuccessText : ts.statusErrorText
1675
- ],
1676
- children: item.text
1677
- }
1678
- )
1679
- },
1680
- i
1681
- );
1682
- }
1683
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: ts.detail, children: item.text }, i);
1684
- }),
1685
- isStreaming ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ts.cursorRow, children: [
1686
- /* @__PURE__ */ jsxRuntime.jsx(PaymanMarkNative, { size: 14 }),
1687
- /* @__PURE__ */ jsxRuntime.jsx(ShimmerText, { style: ts.cursorLabel, children: cursorText })
1688
- ] }) : null
1689
- ] }) : null
1690
- ] });
1691
- }
1692
- function StreamingDot() {
1693
- const opacity = React.useRef(new reactNative.Animated.Value(0.3)).current;
1694
- React.useEffect(() => {
1695
- const anim = reactNative.Animated.loop(
1696
- reactNative.Animated.sequence([
1697
- reactNative.Animated.timing(opacity, { toValue: 1, duration: 700, easing: reactNative.Easing.inOut(reactNative.Easing.ease), useNativeDriver: true }),
1698
- reactNative.Animated.timing(opacity, { toValue: 0.3, duration: 700, easing: reactNative.Easing.inOut(reactNative.Easing.ease), useNativeDriver: true })
1699
- ])
1700
- );
1701
- anim.start();
1702
- return () => anim.stop();
1703
- }, [opacity]);
1704
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [dotStyle.dot, { opacity }] });
1705
- }
1706
- function getFormattedThinking(message, includeInitialPlaceholder) {
1707
- const plain = message.allThinkingText || message.activeThinkingText || "";
1708
- const base = message.formattedThinkingText || paymanTypescriptAskSdk.buildFormattedThinking(message.steps, plain);
1709
- if (includeInitialPlaceholder && base) {
1710
- return INITIAL_THINKING_PLACEHOLDER + "\n" + base;
1711
- }
1712
- if (includeInitialPlaceholder) {
1713
- return INITIAL_THINKING_PLACEHOLDER;
1714
- }
1715
- return base;
1716
- }
1717
- function AssistantMessageV2({
1718
- message,
1719
- onExecutionTraceClick: _onExecutionTraceClick,
1720
- actions
1721
- }) {
1722
- const [copied, setCopied] = React.useState(false);
1723
- const copyTimerRef = React.useRef(null);
1724
- const showCopyAction = actions?.copy ?? true;
1725
- const isHistorical = !!message.isHistorical;
1726
- const hasEverStreamed = React.useRef(!!message.isStreaming && !isHistorical);
1727
- const hasShownInitialThinking = React.useRef(
1728
- !isHistorical && message.streamProgress === "processing"
1729
- );
1730
- if (message.isStreaming && !isHistorical) hasEverStreamed.current = true;
1731
- const fadeIn = React.useRef(new reactNative.Animated.Value(0)).current;
1732
- React.useEffect(() => {
1733
- reactNative.Animated.timing(fadeIn, {
1734
- toValue: 1,
1735
- duration: 220,
1736
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
1737
- useNativeDriver: true
1738
- }).start();
1739
- }, [fadeIn]);
1740
- const rawResponseContent = (() => {
1741
- const raw = message.isStreaming && !isHistorical ? message.streamingContent || message.content : message.content;
1742
- if (!raw) return "";
1743
- return raw.replace(/\\n/g, "\n");
1744
- })();
1745
- const hasReadyStreamPhase = !isHistorical && (message.streamProgress === "processing" || Boolean(message.steps?.length) || Boolean(message.allThinkingText) || Boolean(message.activeThinkingText) || Boolean(rawResponseContent));
1746
- if (hasReadyStreamPhase && !message.isError) {
1747
- hasShownInitialThinking.current = true;
1748
- }
1749
- const includeInitialPlaceholder = !isHistorical && hasShownInitialThinking.current && !message.isError;
1750
- const rawThinkingContent = React.useMemo(
1751
- () => getFormattedThinking(message, includeInitialPlaceholder),
1752
- [message, includeInitialPlaceholder]
1753
- );
1754
- const isThinkingStreaming = !isHistorical && !!message.isStreaming && !rawResponseContent && !message.isError;
1755
- const { displayedText: thinkingContent } = useTypingEffect(
1756
- rawThinkingContent,
1757
- hasEverStreamed.current && isThinkingStreaming,
1758
- THINKING_SPEED,
1759
- includeInitialPlaceholder ? INITIAL_THINKING_PLACEHOLDER : void 0,
1760
- `thinking:${message.id}`
1761
- );
1762
- const hasThinkingContent = Boolean(thinkingContent);
1763
- const showThinkingBlock = !isHistorical && (hasThinkingContent || isThinkingStreaming);
1764
- const showLegacyThinkingPhase = !isHistorical && !!message.isStreaming && !message.isError && !rawResponseContent && message.streamProgress === "started";
1765
- const responseTypingEnabled = !isHistorical && hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
1766
- const { displayedText: displayContent, isTyping: isResponseTyping } = useTypingEffect(
1767
- rawResponseContent,
1768
- responseTypingEnabled,
1769
- RESPONSE_SPEED,
1770
- void 0,
1771
- `response:${message.id}`
1772
- );
1773
- const requestStartedAt = React.useMemo(() => {
1774
- if (!message.timestamp) return void 0;
1775
- const t = Date.parse(message.timestamp);
1776
- return Number.isFinite(t) ? t : void 0;
1777
- }, [message.timestamp]);
1778
- const thinkingDuration = React.useMemo(() => {
1779
- const steps = message.steps;
1780
- if (!steps || steps.length === 0) return void 0;
1781
- const last = steps[steps.length - 1];
1782
- if (requestStartedAt && last.timestamp) {
1783
- return Math.max(0, Math.round((last.timestamp - requestStartedAt) / 1e3));
1784
- }
1785
- const first = steps[0];
1786
- if (first.timestamp && last.timestamp) {
1787
- return Math.round((last.timestamp - first.timestamp) / 1e3);
1788
- }
1789
- const total = steps.reduce((sum, s9) => sum + (s9.elapsedMs || 0), 0);
1790
- return total > 0 ? Math.round(total / 1e3) : void 0;
1791
- }, [message.steps, requestStartedAt]);
1792
- const handleCopy = React.useCallback(() => {
1793
- reactNative.Clipboard.setString(displayContent);
1794
- setCopied(true);
1795
- if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
1796
- copyTimerRef.current = setTimeout(() => setCopied(false), 1800);
1797
- }, [displayContent]);
1798
- React.useEffect(() => {
1799
- return () => {
1800
- if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
1801
- };
1802
- }, []);
1803
- const conflictErrorMessage = getConflictErrorMessage(message.errorDetails);
1804
- const isConflictError = Boolean(conflictErrorMessage);
1805
- const resolvedErrorText = (() => {
1806
- if (conflictErrorMessage) return conflictErrorMessage;
1807
- if (isFriendlyWorkflowError(message.errorDetails) && !message.errorDetails) {
1808
- return "Oops, something went wrong. Please try again.";
1809
- }
1810
- return message.errorDetails;
1811
- })();
1812
- if (isConflictError) {
1813
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [s5.wrapper, { opacity: fadeIn }], children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s5.errorRow, children: [
1814
- /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.AlertCircle, { size: 15, color: "#ef4444", strokeWidth: 2 }),
1815
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { selectable: true, style: s5.errorText, children: conflictErrorMessage })
1816
- ] }) });
1817
- }
1818
- if (message.isError && !displayContent && !hasThinkingContent) {
1819
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [s5.wrapper, { opacity: fadeIn }], children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s5.errorRow, children: [
1820
- /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.AlertCircle, { size: 15, color: "#ef4444", strokeWidth: 2 }),
1821
- resolvedErrorText ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { selectable: true, style: s5.errorText, children: resolvedErrorText }) : null
1822
- ] }) });
1823
- }
1824
- if (showLegacyThinkingPhase && !showThinkingBlock) {
1825
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Animated.View, { style: [s5.wrapper, { opacity: fadeIn }], children: /* @__PURE__ */ jsxRuntime.jsx(ShimmerText, { style: s5.legacyThinking, children: "Working on it\u2026" }) });
1826
- }
1827
- const hasPartialError = message.isError && displayContent && !isConflictError;
1828
- const isCancelled = message.isCancelled;
1829
- const isDone = !message.isStreaming && displayContent && !hasPartialError && !isResponseTyping;
1830
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [s5.wrapper, { opacity: fadeIn }], children: [
1831
- showThinkingBlock ? /* @__PURE__ */ jsxRuntime.jsx(
1832
- ThinkingBlock,
1833
- {
1834
- content: thinkingContent,
1835
- isStreaming: isThinkingStreaming,
1836
- durationSec: message.thinkingDurationSec ?? thinkingDuration,
1837
- startedAt: requestStartedAt
1838
- }
1839
- ) : null,
1840
- displayContent ? isResponseTyping ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { selectable: true, style: s5.plainText, children: displayContent }) : /* @__PURE__ */ jsxRuntime.jsx(Markdown__default.default, { style: mdStyles, children: displayContent }) : !isThinkingStreaming && !hasThinkingContent ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s5.placeholder, children: "\u2026" }) : null,
1841
- isCancelled && message.isStreaming ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s5.pausedRow, children: [
1842
- /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.WifiOff, { size: 14, color: "rgba(217, 119, 6, 0.85)", strokeWidth: 2 }),
1843
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s5.pausedText, children: "Connection slow \u2014 resuming\u2026" })
1844
- ] }) : null,
1845
- hasPartialError ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s5.errorRow, children: [
1846
- /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.AlertCircle, { size: 15, color: "#ef4444", strokeWidth: 2 }),
1847
- resolvedErrorText ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { selectable: true, style: s5.errorText, children: resolvedErrorText }) : null
1848
- ] }) : null,
1849
- message.isStreaming && displayContent && !isCancelled ? /* @__PURE__ */ jsxRuntime.jsx(StreamingDot, {}) : null,
1850
- isDone && showCopyAction ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s5.actions, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: handleCopy, hitSlop: 8, style: s5.actionBtn, children: copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Check, { size: 14, color: "#059669", strokeWidth: 2.5 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.Copy, { size: 14, color: TEXT_TERTIARY, strokeWidth: 2 }) }) }) : null
1851
- ] });
1852
- }
1853
- var s5 = reactNative.StyleSheet.create({
1854
- wrapper: {
1855
- paddingHorizontal: 16,
1856
- paddingVertical: 6,
1857
- alignItems: "flex-start"
1858
- },
1859
- plainText: {
1860
- fontSize: 15,
1861
- lineHeight: 23,
1862
- color: TEXT_PRIMARY2
1863
- },
1864
- placeholder: {
1865
- fontSize: 14,
1866
- color: TEXT_MUTED
1867
- },
1868
- legacyThinking: {
1869
- fontSize: 14,
1870
- color: TEXT_SECONDARY2,
1871
- fontStyle: "italic"
1872
- },
1873
- errorRow: {
1874
- flexDirection: "row",
1875
- alignItems: "flex-start",
1876
- gap: 8,
1877
- marginTop: 4
1878
- },
1879
- errorText: {
1880
- flex: 1,
1881
- fontSize: 14,
1882
- color: "#ef4444",
1883
- lineHeight: 20
1884
- },
1885
- pausedRow: {
1886
- flexDirection: "row",
1887
- alignItems: "center",
1888
- gap: 6,
1889
- marginTop: 6
1890
- },
1891
- pausedText: {
1892
- fontSize: 13,
1893
- color: "rgba(217, 119, 6, 0.95)"
1894
- },
1895
- actions: {
1896
- flexDirection: "row",
1897
- marginTop: 6,
1898
- gap: 4
1899
- },
1900
- actionBtn: {
1901
- padding: 6,
1902
- borderRadius: 8
1903
- }
1904
- });
1905
- var ts = reactNative.StyleSheet.create({
1906
- container: {
1907
- marginBottom: 8,
1908
- paddingLeft: 4,
1909
- alignSelf: "stretch"
1910
- },
1911
- header: {
1912
- flexDirection: "row",
1913
- alignItems: "center",
1914
- gap: 8,
1915
- paddingVertical: 4
1916
- },
1917
- headerLabelRow: {
1918
- flexDirection: "row",
1919
- alignItems: "center"
1920
- },
1921
- headerLabel: {
1922
- fontSize: 13,
1923
- color: TEXT_SECONDARY2,
1924
- fontWeight: "500"
1925
- },
1926
- headerLabelStatic: {
1927
- fontSize: 13,
1928
- color: TEXT_SECONDARY2,
1929
- fontWeight: "500"
1930
- },
1931
- timerText: {
1932
- fontSize: 13,
1933
- color: TEXT_TERTIARY,
1934
- marginLeft: 6,
1935
- fontVariant: ["tabular-nums"]
1936
- },
1937
- body: {
1938
- marginTop: 6,
1939
- marginLeft: 22,
1940
- paddingLeft: 12,
1941
- borderLeftWidth: 2,
1942
- borderLeftColor: "rgba(0,133,141,0.15)",
1943
- gap: 4
1944
- },
1945
- statement: {
1946
- fontSize: 13,
1947
- fontWeight: "500",
1948
- color: TEXT_PRIMARY2,
1949
- marginTop: 4
1950
- },
1951
- detail: {
1952
- fontSize: 13,
1953
- color: TEXT_SECONDARY2,
1954
- lineHeight: 20
1955
- },
1956
- statusBadge: {
1957
- alignSelf: "flex-start",
1958
- borderRadius: 999,
1959
- paddingHorizontal: 10,
1960
- paddingVertical: 3,
1961
- borderWidth: reactNative.StyleSheet.hairlineWidth,
1962
- marginTop: 2
1963
- },
1964
- statusBadgeText: {
1965
- fontSize: 11,
1966
- fontWeight: "500"
1967
- },
1968
- statusSuccessBg: {
1969
- backgroundColor: "rgba(5,150,105,0.06)",
1970
- borderColor: "rgba(5,150,105,0.18)"
1971
- },
1972
- statusSuccessText: { color: "#059669" },
1973
- statusErrorBg: {
1974
- backgroundColor: "rgba(239,68,68,0.06)",
1975
- borderColor: "rgba(239,68,68,0.18)"
1976
- },
1977
- statusErrorText: { color: "#ef4444" },
1978
- cursorRow: {
1979
- flexDirection: "row",
1980
- alignItems: "center",
1981
- gap: 8,
1982
- marginTop: 4,
1983
- minHeight: 18
1984
- },
1985
- cursorLabel: {
1986
- fontSize: 13,
1987
- color: TEXT_SECONDARY2
1988
- }
1989
- });
1990
- var dotStyle = reactNative.StyleSheet.create({
1991
- dot: {
1992
- width: 7,
1993
- height: 7,
1994
- borderRadius: 4,
1995
- backgroundColor: PAYMAN_TEAL2,
1996
- marginTop: 6
1997
- }
1998
- });
1999
- var mark = reactNative.StyleSheet.create({
2000
- wrap: {
2001
- alignItems: "center",
2002
- justifyContent: "center",
2003
- position: "relative"
2004
- },
2005
- ring: {
2006
- position: "absolute",
2007
- borderWidth: 1.5,
2008
- borderColor: PAYMAN_TEAL2,
2009
- backgroundColor: "transparent"
2010
- },
2011
- core: {
2012
- backgroundColor: PAYMAN_TEAL2
2013
- }
2014
- });
2015
- var mdStyles = reactNative.StyleSheet.create({
2016
- body: {
2017
- fontSize: 15,
2018
- lineHeight: 23,
2019
- color: TEXT_PRIMARY2
2020
- },
2021
- heading1: { fontSize: 20, fontWeight: "700", color: TEXT_PRIMARY2, marginTop: 12, marginBottom: 4 },
2022
- heading2: { fontSize: 17, fontWeight: "700", color: TEXT_PRIMARY2, marginTop: 10, marginBottom: 4 },
2023
- heading3: { fontSize: 15, fontWeight: "600", color: TEXT_PRIMARY2, marginTop: 8, marginBottom: 2 },
2024
- strong: { fontWeight: "700" },
2025
- em: { fontStyle: "italic" },
2026
- code_inline: {
2027
- fontFamily: reactNative.Platform.OS === "ios" ? "Menlo" : "monospace",
2028
- fontSize: 13,
2029
- backgroundColor: "rgba(15,23,42,0.06)",
2030
- borderRadius: 4,
2031
- paddingHorizontal: 4,
2032
- color: TEXT_PRIMARY2
2033
- },
2034
- fence: {
2035
- backgroundColor: "rgba(15,23,42,0.05)",
2036
- borderRadius: 8,
2037
- padding: 12,
2038
- marginVertical: 6,
2039
- fontFamily: reactNative.Platform.OS === "ios" ? "Menlo" : "monospace",
2040
- fontSize: 13,
2041
- color: TEXT_PRIMARY2
2042
- },
2043
- blockquote: {
2044
- borderLeftWidth: 3,
2045
- borderLeftColor: "#cbd5e1",
2046
- paddingLeft: 12,
2047
- marginLeft: 0,
2048
- marginVertical: 4,
2049
- opacity: 0.8
2050
- },
2051
- bullet_list: { marginVertical: 4 },
2052
- ordered_list: { marginVertical: 4 },
2053
- list_item: { marginVertical: 2 },
2054
- hr: { backgroundColor: "rgba(15,23,42,0.1)", height: 1, marginVertical: 8 },
2055
- link: { color: PAYMAN_TEAL2, textDecorationLine: "underline" },
2056
- table: {
2057
- borderWidth: 1,
2058
- borderColor: "rgba(15,23,42,0.1)",
2059
- borderRadius: 6,
2060
- marginVertical: 6,
2061
- overflow: "hidden"
2062
- },
2063
- th: { backgroundColor: "rgba(15,23,42,0.05)", padding: 8, fontWeight: "600", fontSize: 13 },
2064
- td: {
2065
- padding: 8,
2066
- fontSize: 13,
2067
- borderTopWidth: 1,
2068
- borderTopColor: "rgba(15,23,42,0.07)"
2069
- }
2070
- });
2071
- var NEAR_BOTTOM_THRESHOLD = 120;
2072
- var MessageListV2 = React.forwardRef(
2073
- function MessageListV22({
2074
- messages,
2075
- isLoadingSession = false,
2076
- onEditUserMessage,
2077
- onRetryUserMessage,
2078
- onImageClick: _onImageClick,
2079
- onExecutionTraceClick,
2080
- messageActions,
2081
- retryDisabled = false
2082
- // userAction props are handled by the modal in PaymanChat.native — ignored here
2083
- }, ref) {
2084
- const scrollViewRef = React.useRef(null);
2085
- const followingBottomRef = React.useRef(true);
2086
- const isProgrammaticScrollRef = React.useRef(false);
2087
- const prevCountRef = React.useRef(messages.length);
2088
- const [showScrollBtn, setShowScrollBtn] = React.useState(false);
2089
- const scrollToBottom = React.useCallback((animated = false) => {
2090
- isProgrammaticScrollRef.current = true;
2091
- followingBottomRef.current = true;
2092
- setShowScrollBtn(false);
2093
- scrollViewRef.current?.scrollToEnd({ animated });
2094
- const clear = () => {
2095
- isProgrammaticScrollRef.current = false;
2096
- };
2097
- if (animated) {
2098
- setTimeout(clear, 400);
2099
- } else {
2100
- requestAnimationFrame(clear);
2101
- }
2102
- }, []);
2103
- React.useImperativeHandle(
2104
- ref,
2105
- () => ({
2106
- scrollToBottom: (animated = false) => scrollToBottom(animated)
2107
- }),
2108
- [scrollToBottom]
2109
- );
2110
- React.useEffect(() => {
2111
- const prevCount = prevCountRef.current;
2112
- prevCountRef.current = messages.length;
2113
- if (messages.length > prevCount) {
2114
- const last = messages[messages.length - 1];
2115
- if (last?.role === "user" || followingBottomRef.current) {
2116
- followingBottomRef.current = true;
2117
- requestAnimationFrame(() => scrollToBottom(false));
2118
- }
2119
- }
2120
- }, [messages.length, scrollToBottom]);
2121
- React.useEffect(() => {
2122
- if (messages.length > 0) {
2123
- setTimeout(() => scrollToBottom(false), 50);
2124
- }
2125
- }, []);
2126
- const handleContentSizeChange = React.useCallback(() => {
2127
- if (followingBottomRef.current) {
2128
- scrollViewRef.current?.scrollToEnd({ animated: false });
2129
- }
2130
- }, []);
2131
- const handleScroll = React.useCallback(
2132
- (e) => {
2133
- if (isProgrammaticScrollRef.current) return;
2134
- const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent;
2135
- const distanceFromBottom = contentSize.height - contentOffset.y - layoutMeasurement.height;
2136
- const nearBottom = distanceFromBottom <= NEAR_BOTTOM_THRESHOLD;
2137
- followingBottomRef.current = nearBottom;
2138
- setShowScrollBtn(!nearBottom);
2139
- },
2140
- []
2141
- );
2142
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s6.root, children: [
2143
- /* @__PURE__ */ jsxRuntime.jsxs(
2144
- reactNative.ScrollView,
2145
- {
2146
- ref: scrollViewRef,
2147
- style: s6.scroll,
2148
- contentContainerStyle: s6.content,
2149
- onScroll: handleScroll,
2150
- onContentSizeChange: handleContentSizeChange,
2151
- scrollEventThrottle: 16,
2152
- keyboardDismissMode: "interactive",
2153
- keyboardShouldPersistTaps: "handled",
2154
- children: [
2155
- isLoadingSession ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s6.loadingContainer, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { size: "large", color: "#00858d" }) }) : messages.map(
2156
- (message) => message.role === "user" ? /* @__PURE__ */ jsxRuntime.jsx(
2157
- UserMessageV2,
2158
- {
2159
- message,
2160
- actions: messageActions?.userMessageActions,
2161
- onEdit: onEditUserMessage,
2162
- onRetry: onRetryUserMessage,
2163
- retryDisabled
2164
- },
2165
- message.id
2166
- ) : /* @__PURE__ */ jsxRuntime.jsx(
2167
- AssistantMessageV2,
2168
- {
2169
- message,
2170
- onExecutionTraceClick,
2171
- actions: messageActions?.assistantMessageActions
2172
- },
2173
- message.id
2174
- )
2175
- ),
2176
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s6.bottomPad })
2177
- ]
2178
- }
2179
- ),
2180
- showScrollBtn ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s6.scrollBtnContainer, pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(
2181
- reactNative.Pressable,
2182
- {
2183
- style: s6.scrollBtn,
2184
- onPress: () => scrollToBottom(true),
2185
- hitSlop: 8,
2186
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReactNative.ArrowDown, { size: 16, color: "#555", strokeWidth: 2.5 })
2187
- }
2188
- ) }) : null
2189
- ] });
2190
- }
2191
- );
2192
- var s6 = reactNative.StyleSheet.create({
2193
- root: {
2194
- flex: 1,
2195
- minHeight: 0,
2196
- position: "relative"
2197
- },
2198
- scroll: {
2199
- flex: 1
2200
- },
2201
- content: {
2202
- flexGrow: 1,
2203
- paddingTop: 12,
2204
- paddingBottom: 4
2205
- },
2206
- loadingContainer: {
2207
- flex: 1,
2208
- alignItems: "center",
2209
- justifyContent: "center",
2210
- paddingVertical: 60
2211
- },
2212
- bottomPad: {
2213
- height: 16
2214
- },
2215
- scrollBtnContainer: {
2216
- position: "absolute",
2217
- bottom: 16,
2218
- left: 0,
2219
- right: 0,
2220
- alignItems: "center"
2221
- },
2222
- scrollBtn: {
2223
- backgroundColor: "#fff",
2224
- borderRadius: 20,
2225
- width: 36,
2226
- height: 36,
2227
- alignItems: "center",
2228
- justifyContent: "center",
2229
- shadowColor: "#000",
2230
- shadowOffset: { width: 0, height: 2 },
2231
- shadowOpacity: 0.12,
2232
- shadowRadius: 6,
2233
- elevation: 4,
2234
- borderWidth: 1,
2235
- borderColor: "rgba(0,0,0,0.07)"
2236
- }
2237
- });
2238
- var COMPACT_BREAKPOINT = 560;
2239
- var PAYMAN_GREEN = "#0A3B44";
2240
- function getCategoryGlyph(label) {
2241
- const normalizedLabel = label.toLowerCase();
2242
- if (normalizedLabel.includes("pay") || normalizedLabel.includes("transfer")) return "->";
2243
- if (normalizedLabel.includes("account")) return "[]";
2244
- if (normalizedLabel.includes("insight") || normalizedLabel.includes("analytics")) return "/_";
2245
- if (normalizedLabel.includes("product")) return "o";
2246
- return "+";
2247
- }
2248
- function formatSuggestionTitle(value) {
2249
- return value.replace(
2250
- /\b(Good (?:morning|afternoon|evening))[ \t]+(?=how can I help)/i,
2251
- "$1 - "
2252
- );
2253
- }
2254
- function PromptSuggestionsV2({
2255
- title,
2256
- categories,
2257
- disabled = false,
2258
- onPromptClick
2259
- }) {
2260
- const { width } = reactNative.useWindowDimensions();
2261
- const isCompact = width < COMPACT_BREAKPOINT;
2262
- const validCategories = React.useMemo(
2263
- () => categories.filter((category) => category.prompts.length > 0),
2264
- [categories]
2265
- );
2266
- const [activeCategoryIndex, setActiveCategoryIndex] = React.useState(null);
2267
- React.useEffect(() => {
2268
- if (activeCategoryIndex != null && (activeCategoryIndex < 0 || activeCategoryIndex >= validCategories.length)) {
2269
- setActiveCategoryIndex(null);
2270
- }
2271
- }, [activeCategoryIndex, validCategories.length]);
2272
- if (validCategories.length === 0) return null;
2273
- const activeCategory = activeCategoryIndex == null ? null : validCategories[activeCategoryIndex] ?? null;
2274
- const displayTitle = title ? formatSuggestionTitle(title) : void 0;
2275
- const categoryButtons = validCategories.map((category, categoryIndex) => {
2276
- const isActive = activeCategoryIndex === categoryIndex;
2277
- return /* @__PURE__ */ jsxRuntime.jsxs(
2278
- reactNative.Pressable,
2279
- {
2280
- accessibilityRole: "button",
2281
- accessibilityLabel: `${category.label} prompts`,
2282
- accessibilityState: { expanded: isActive, selected: isActive },
2283
- onPress: () => setActiveCategoryIndex(
2284
- (currentIndex) => currentIndex === categoryIndex ? null : categoryIndex
2285
- ),
2286
- style: ({ pressed }) => [
2287
- styles.categoryButton,
2288
- isActive && styles.categoryButtonActive,
2289
- pressed && styles.categoryButtonPressed
2290
- ],
2291
- children: [
2292
- category.icon ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.customIcon, children: category.icon }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.categoryIcon, isActive && styles.categoryIconActive], children: getCategoryGlyph(category.label) }),
2293
- /* @__PURE__ */ jsxRuntime.jsx(
2294
- reactNative.Text,
2295
- {
2296
- numberOfLines: 1,
2297
- style: [styles.categoryText, isActive && styles.categoryTextActive],
2298
- children: category.label
2299
- }
2300
- )
2301
- ]
2302
- },
2303
- `${category.label}-${categoryIndex}`
2304
- );
2305
- });
2306
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.root, children: [
2307
- displayTitle ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.title, isCompact && styles.titleCompact], children: displayTitle }) : null,
2308
- isCompact ? /* @__PURE__ */ jsxRuntime.jsx(
2309
- reactNative.ScrollView,
2310
- {
2311
- horizontal: true,
2312
- showsHorizontalScrollIndicator: false,
2313
- contentContainerStyle: styles.categoryScrollContent,
2314
- style: styles.categoryScroll,
2315
- children: categoryButtons
2316
- }
2317
- ) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.categoryWrap, children: categoryButtons }),
2318
- activeCategory ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.promptCard, isCompact && styles.promptCardCompact], children: activeCategory.prompts.map((prompt, promptIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
2319
- reactNative.Pressable,
2320
- {
2321
- accessibilityRole: "button",
2322
- accessibilityLabel: `Use suggested prompt: ${prompt}`,
2323
- disabled,
2324
- onPress: () => onPromptClick(prompt),
2325
- style: ({ pressed }) => [
2326
- styles.promptButton,
2327
- promptIndex > 0 && styles.promptButtonDivider,
2328
- pressed && !disabled && styles.promptButtonPressed,
2329
- disabled && styles.promptButtonDisabled
2330
- ],
2331
- children: [
2332
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.promptText, children: prompt }),
2333
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.promptArrow, children: "->" })
2334
- ]
2335
- },
2336
- `${prompt}-${promptIndex}`
2337
- )) }) : null
2338
- ] });
2339
- }
2340
- var styles = reactNative.StyleSheet.create({
2341
- root: {
2342
- width: "100%",
2343
- alignItems: "center"
2344
- },
2345
- title: {
2346
- marginBottom: 8,
2347
- color: "#18181b",
2348
- fontSize: 16,
2349
- fontWeight: "500",
2350
- lineHeight: 24,
2351
- textAlign: "center"
2352
- },
2353
- titleCompact: {
2354
- maxWidth: 340,
2355
- paddingHorizontal: 16,
2356
- fontSize: 16,
2357
- lineHeight: 24
2358
- },
2359
- categoryWrap: {
2360
- width: "100%",
2361
- flexDirection: "row",
2362
- flexWrap: "wrap",
2363
- justifyContent: "center",
2364
- marginBottom: 12
2365
- },
2366
- categoryScroll: {
2367
- width: "100%",
2368
- marginBottom: 12
2369
- },
2370
- categoryScrollContent: {
2371
- paddingHorizontal: 16,
2372
- paddingVertical: 2
2373
- },
2374
- categoryButton: {
2375
- minHeight: 30,
2376
- flexDirection: "row",
2377
- alignItems: "center",
2378
- justifyContent: "center",
2379
- marginHorizontal: 4,
2380
- marginVertical: 4,
2381
- paddingHorizontal: 12,
2382
- paddingVertical: 6,
2383
- borderWidth: 1,
2384
- borderColor: "#e4e4e7",
2385
- borderRadius: 999,
2386
- backgroundColor: "#ffffff"
2387
- },
2388
- categoryButtonActive: {
2389
- borderColor: PAYMAN_GREEN,
2390
- backgroundColor: PAYMAN_GREEN
2391
- },
2392
- categoryButtonPressed: {
2393
- opacity: 0.82
2394
- },
2395
- customIcon: {
2396
- marginRight: 6
2397
- },
2398
- categoryIcon: {
2399
- marginRight: 6,
2400
- color: PAYMAN_GREEN,
2401
- fontSize: 11,
2402
- fontWeight: "700",
2403
- lineHeight: 14
2404
- },
2405
- categoryIconActive: {
2406
- color: "#ffffff"
2407
- },
2408
- categoryText: {
2409
- maxWidth: 180,
2410
- color: "#3f3f46",
2411
- fontSize: 11,
2412
- fontWeight: "600",
2413
- lineHeight: 14
2414
- },
2415
- categoryTextActive: {
2416
- color: "#ffffff"
2417
- },
2418
- promptCard: {
2419
- width: "100%",
2420
- maxWidth: 600,
2421
- overflow: "hidden",
2422
- borderWidth: 1,
2423
- borderColor: "#e4e4e7",
2424
- borderRadius: 16,
2425
- backgroundColor: "#ffffff"
2426
- },
2427
- promptCardCompact: {
2428
- width: "auto",
2429
- alignSelf: "stretch",
2430
- marginHorizontal: 16,
2431
- borderRadius: 14
2432
- },
2433
- promptButton: {
2434
- minHeight: 50,
2435
- flexDirection: "row",
2436
- alignItems: "center",
2437
- justifyContent: "space-between",
2438
- paddingHorizontal: 16,
2439
- paddingVertical: 12
2440
- },
2441
- promptButtonDivider: {
2442
- borderTopWidth: 1,
2443
- borderTopColor: "#e4e4e7"
2444
- },
2445
- promptButtonPressed: {
2446
- backgroundColor: "#eef7f7"
2447
- },
2448
- promptButtonDisabled: {
2449
- opacity: 0.5
2450
- },
2451
- promptText: {
2452
- flex: 1,
2453
- paddingRight: 12,
2454
- color: "#27272a",
2455
- fontSize: 13,
2456
- lineHeight: 18
2457
- },
2458
- promptArrow: {
2459
- color: PAYMAN_GREEN,
2460
- fontSize: 13,
2461
- lineHeight: 16
2462
- }
2463
- });
2464
- var WaveformBar = React.memo(
2465
- ({
2466
- delay,
2467
- isActive,
2468
- color
2469
- }) => {
2470
- const height = React.useRef(new reactNative.Animated.Value(6)).current;
2471
- React.useEffect(() => {
2472
- if (isActive) {
2473
- const animation = reactNative.Animated.loop(
2474
- reactNative.Animated.sequence([
2475
- reactNative.Animated.timing(height, {
2476
- toValue: 14 + Math.random() * 10,
2477
- duration: 300 + Math.random() * 50,
2478
- delay,
2479
- easing: reactNative.Easing.inOut(reactNative.Easing.ease),
2480
- useNativeDriver: false
2481
- }),
2482
- reactNative.Animated.timing(height, {
2483
- toValue: 6 + Math.random() * 4,
2484
- duration: 300 + Math.random() * 50,
2485
- easing: reactNative.Easing.inOut(reactNative.Easing.ease),
2486
- useNativeDriver: false
2487
- })
2488
- ])
2489
- );
2490
- animation.start();
2491
- return () => animation.stop();
2492
- } else {
2493
- reactNative.Animated.timing(height, {
2494
- toValue: 6,
2495
- duration: 300,
2496
- useNativeDriver: false
2497
- }).start();
2498
- }
2499
- }, [isActive, delay, height]);
2500
- return /* @__PURE__ */ jsxRuntime.jsx(
2501
- reactNative.Animated.View,
2502
- {
2503
- style: [styles2.waveformBar, { height, backgroundColor: color }]
2504
- }
2505
- );
2506
- }
2507
- );
2508
- WaveformBar.displayName = "WaveformBar";
2509
- var RollingText = React.memo(({ text, color }) => {
2510
- const [lines, setLines] = React__default.default.useState([]);
2511
- const [currentLineIndex, setCurrentLineIndex] = React__default.default.useState(0);
2512
- const translateY = React.useRef(new reactNative.Animated.Value(0)).current;
2513
- const opacity = React.useRef(new reactNative.Animated.Value(1)).current;
2514
- React.useEffect(() => {
2515
- if (!text) {
2516
- setLines([]);
2517
- setCurrentLineIndex(0);
2518
- return;
2519
- }
2520
- const words = text.split(" ");
2521
- const newLines = [];
2522
- let currentLine = "";
2523
- words.forEach((word) => {
2524
- const testLine = currentLine ? `${currentLine} ${word}` : word;
2525
- if (testLine.length > 35 && currentLine) {
2526
- newLines.push(currentLine);
2527
- currentLine = word;
2528
- } else {
2529
- currentLine = testLine;
2530
- }
2531
- });
2532
- if (currentLine) {
2533
- newLines.push(currentLine);
2534
- }
2535
- setLines(newLines);
2536
- if (newLines.length > 0) {
2537
- const newIndex = newLines.length - 1;
2538
- if (newIndex > currentLineIndex) {
2539
- reactNative.Animated.sequence([
2540
- reactNative.Animated.parallel([
2541
- reactNative.Animated.timing(translateY, {
2542
- toValue: -20,
2543
- duration: 150,
2544
- useNativeDriver: true
2545
- }),
2546
- reactNative.Animated.timing(opacity, {
2547
- toValue: 0,
2548
- duration: 150,
2549
- useNativeDriver: true
2550
- })
2551
- ]),
2552
- reactNative.Animated.timing(translateY, {
2553
- toValue: 20,
2554
- duration: 0,
2555
- useNativeDriver: true
2556
- }),
2557
- reactNative.Animated.parallel([
2558
- reactNative.Animated.timing(translateY, {
2559
- toValue: 0,
2560
- duration: 200,
2561
- easing: reactNative.Easing.out(reactNative.Easing.ease),
2562
- useNativeDriver: true
2563
- }),
2564
- reactNative.Animated.timing(opacity, {
2565
- toValue: 1,
2566
- duration: 200,
2567
- useNativeDriver: true
2568
- })
2569
- ])
2570
- ]).start();
2571
- setCurrentLineIndex(newIndex);
2572
- }
2573
- }
2574
- }, [text, currentLineIndex, translateY, opacity]);
2575
- const currentText = lines[currentLineIndex] || text;
2576
- return /* @__PURE__ */ jsxRuntime.jsx(
2577
- reactNative.Animated.Text,
2578
- {
2579
- style: [
2580
- styles2.transcribedText,
2581
- {
2582
- color,
2583
- opacity,
2584
- transform: [{ translateY }]
2585
- }
2586
- ],
2587
- numberOfLines: 1,
2588
- children: currentText
2589
- }
2590
- );
2591
- });
2592
- RollingText.displayName = "RollingText";
2593
- var VoiceOverlay = React.memo(
2594
- ({
2595
- visible,
2596
- voiceState,
2597
- transcribedText,
2598
- onStopRecording
2599
- }) => {
2600
- const panelHeight = React.useRef(new reactNative.Animated.Value(0)).current;
2601
- const contentOpacity = React.useRef(new reactNative.Animated.Value(0)).current;
2602
- const [mounted, setMounted] = React.useState(false);
2603
- const isListening = voiceState === "listening";
2604
- const PANEL_CONTENT_HEIGHT = 160;
2605
- const bgColor = "#15687d";
2606
- const textColor = "#ffffff";
2607
- const mutedColor = "rgba(255,255,255,0.75)";
2608
- const primaryColor = "#ffffff";
2609
- React.useEffect(() => {
2610
- if (visible) {
2611
- setMounted(true);
2612
- reactNative.Animated.sequence([
2613
- reactNative.Animated.timing(panelHeight, {
2614
- toValue: PANEL_CONTENT_HEIGHT,
2615
- duration: 300,
2616
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
2617
- useNativeDriver: false
2618
- }),
2619
- reactNative.Animated.timing(contentOpacity, {
2620
- toValue: 1,
2621
- duration: 150,
2622
- useNativeDriver: true
2623
- })
2624
- ]).start();
2625
- } else {
2626
- reactNative.Animated.parallel([
2627
- reactNative.Animated.timing(contentOpacity, {
2628
- toValue: 0,
2629
- duration: 100,
2630
- useNativeDriver: true
2631
- }),
2632
- reactNative.Animated.timing(panelHeight, {
2633
- toValue: 0,
2634
- duration: 220,
2635
- easing: reactNative.Easing.in(reactNative.Easing.ease),
2636
- useNativeDriver: false
2637
- })
2638
- ]).start(() => setMounted(false));
2639
- }
2640
- }, [visible, panelHeight, contentOpacity]);
2641
- if (!mounted) return null;
2642
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onStopRecording, style: styles2.panelWrapper, children: /* @__PURE__ */ jsxRuntime.jsx(
2643
- reactNative.Animated.View,
2644
- {
2645
- style: [
2646
- styles2.panel,
2647
- {
2648
- height: panelHeight,
2649
- backgroundColor: bgColor
2650
- }
2651
- ],
2652
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles2.content, { opacity: contentOpacity }], children: [
2653
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.textContainer, children: transcribedText.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(RollingText, { text: transcribedText, color: textColor }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.placeholderText, { color: mutedColor }], children: "Listening..." }) }),
2654
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.controlsContainer, children: [
2655
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.waveformContainer, children: [0, 1, 2, 3, 4].map((index) => /* @__PURE__ */ jsxRuntime.jsx(
2656
- WaveformBar,
2657
- {
2658
- delay: index * 20,
2659
- isActive: isListening,
2660
- color: primaryColor
2661
- },
2662
- index
2663
- )) }),
2664
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.hintText, { color: mutedColor }], children: "Tap to stop" })
2665
- ] })
2666
- ] })
2667
- }
2668
- ) });
2669
- }
2670
- );
2671
- VoiceOverlay.displayName = "VoiceOverlay";
2672
- var styles2 = reactNative.StyleSheet.create({
2673
- panelWrapper: {
2674
- zIndex: 10,
2675
- elevation: 10
2676
- },
2677
- panel: {
2678
- overflow: "hidden",
2679
- borderTopLeftRadius: 20,
2680
- borderTopRightRadius: 20
2681
- },
2682
- content: {
2683
- flex: 1,
2684
- paddingHorizontal: 24,
2685
- paddingTop: 20
2686
- },
2687
- textContainer: {
2688
- height: 32,
2689
- justifyContent: "center",
2690
- marginBottom: 16,
2691
- overflow: "hidden"
2692
- },
2693
- transcribedText: {
2694
- fontSize: 18,
2695
- fontWeight: "500",
2696
- textAlign: "left"
2697
- },
2698
- placeholderText: {
2699
- fontSize: 15,
2700
- textAlign: "left",
2701
- fontWeight: "800"
2702
- },
2703
- controlsContainer: {
2704
- flex: 1,
2705
- alignItems: "center",
2706
- justifyContent: "flex-start"
2707
- },
2708
- waveformContainer: {
2709
- flexDirection: "row",
2710
- alignItems: "center",
2711
- justifyContent: "center",
2712
- gap: 5,
2713
- height: 28,
2714
- marginBottom: 12
2715
- },
2716
- waveformBar: {
2717
- width: 3,
2718
- borderRadius: 2
2719
- },
2720
- hintText: {
2721
- fontSize: 12
2722
- }
2723
- });
2724
- var DEFAULT_ACCENT = "#00858d";
2725
- function getPalette(theme, accent) {
2726
- if (theme === "dark") {
2727
- return {
2728
- background: "#0d1719",
2729
- textPrimary: "#f5f8f8",
2730
- textSecondary: "rgba(245,248,248,0.72)",
2731
- textMuted: "rgba(245,248,248,0.45)",
2732
- heroHaloFrom: "rgba(0,133,141,0.16)",
2733
- heroHaloTo: "rgba(0,133,141,0.06)",
2734
- heroRing: "rgba(0,133,141,0.35)",
2735
- heroDot: accent,
2736
- disabledText: "rgba(245,248,248,0.45)"
2737
- };
2738
- }
2739
- return {
2740
- background: "#ffffff",
2741
- textPrimary: "#1d1d1f",
2742
- textSecondary: "#5e5d5a",
2743
- textMuted: "#8a8a86",
2744
- heroHaloFrom: "rgba(0,133,141,0.10)",
2745
- heroHaloTo: "rgba(0,133,141,0.02)",
2746
- heroRing: "rgba(0,133,141,0.20)",
2747
- heroDot: accent,
2748
- disabledText: "#8a8a86"
2749
- };
2750
- }
2751
- var PaymanChat = React.forwardRef(
2752
- function PaymanChat2({ config, callbacks = {}, children }, ref) {
2753
- const {
2754
- messages,
2755
- sendMessage,
2756
- clearMessages,
2757
- cancelStream,
2758
- resetSession: resetChatSession,
2759
- getSessionId,
2760
- getMessages,
2761
- isWaitingForResponse,
2762
- userActionState,
2763
- approveUserAction,
2764
- rejectUserAction,
2765
- resendOtp,
2766
- loadSession,
2767
- loadingSessionId
2768
- } = paymanTypescriptAskSdk.useChatV2(config, callbacks);
2769
- const voiceEnabled = config.ui?.input?.voice === true || typeof config.ui?.input?.voice === "object" && config.ui.input.voice !== null;
2770
- const {
2771
- voiceState,
2772
- transcribedText,
2773
- isAvailable: voiceAvailable,
2774
- isRecording,
2775
- startRecording,
2776
- stopRecording,
2777
- clearTranscript
2778
- } = paymanTypescriptAskSdk.useVoice();
2779
- const attachmentsCfg = config.ui?.input?.attachments;
2780
- const attachmentsAllowed = attachmentsCfg === void 0 || attachmentsCfg === true || typeof attachmentsCfg === "object" && attachmentsCfg !== null;
2781
- const showUploadImage = attachmentsAllowed && (typeof attachmentsCfg !== "object" || attachmentsCfg === null ? true : attachmentsCfg.uploadImage !== false);
2782
- const showAttachFile = attachmentsAllowed && (typeof attachmentsCfg !== "object" || attachmentsCfg === null ? true : attachmentsCfg.attachFile !== false);
2783
- const [hasEverSentMessage, setHasEverSentMessage] = React.useState(false);
2784
- const chatInputRef = React.useRef(null);
2785
- const messageListRef = React.useRef(null);
2786
- const resetToEmptyRef = React.useRef(false);
2787
- React.useEffect(() => {
2788
- if (resetToEmptyRef.current) {
2789
- if (messages.length === 0) {
2790
- setHasEverSentMessage(false);
2791
- resetToEmptyRef.current = false;
2792
- }
2793
- return;
2794
- }
2795
- if (messages.length > 0 && !hasEverSentMessage) {
2796
- setHasEverSentMessage(true);
2797
- }
2798
- }, [messages.length, hasEverSentMessage]);
2799
- const prevRecordingRef = React.useRef(isRecording);
2800
- React.useEffect(() => {
2801
- const wasRecording = prevRecordingRef.current;
2802
- prevRecordingRef.current = isRecording;
2803
- if (wasRecording && !isRecording && transcribedText.trim()) {
2804
- chatInputRef.current?.setDraft(transcribedText.trim());
2805
- }
2806
- }, [isRecording, transcribedText]);
2807
- const handleVoicePress = React.useCallback(async () => {
2808
- if (!voiceAvailable) return;
2809
- if (isRecording) {
2810
- stopRecording();
2811
- return;
2812
- }
2813
- clearTranscript();
2814
- await startRecording();
2815
- }, [voiceAvailable, isRecording, stopRecording, clearTranscript, startRecording]);
2816
- const handleSend = React.useCallback(
2817
- (text) => {
2818
- if (isRecording) stopRecording();
2819
- if (!text.trim()) return;
2820
- void sendMessage(text.trim());
2821
- },
2822
- [isRecording, stopRecording, sendMessage]
2823
- );
2824
- const dismissKeyboard = React.useCallback(() => {
2825
- reactNative.Keyboard.dismiss();
2826
- }, []);
2827
- const handleEditMessageDraft = React.useCallback(
2828
- (messageId) => {
2829
- const targetMessage = messages.find(
2830
- (message) => message.id === messageId && message.role === "user"
2831
- );
2832
- if (!targetMessage?.content.trim()) return;
2833
- chatInputRef.current?.setDraft(targetMessage.content);
2834
- requestAnimationFrame(() => {
2835
- messageListRef.current?.scrollToBottom(true);
2836
- });
2837
- },
2838
- [messages]
2839
- );
2840
- const handleRetryUserMessage = React.useCallback(
2841
- (messageId) => {
2842
- if (isWaitingForResponse) return;
2843
- const targetMessage = messages.find(
2844
- (message) => message.id === messageId && message.role === "user"
2845
- );
2846
- if (!targetMessage?.content.trim()) return;
2847
- void sendMessage(targetMessage.content.trim());
2848
- requestAnimationFrame(() => {
2849
- messageListRef.current?.scrollToBottom(false);
2850
- });
2851
- },
2852
- [isWaitingForResponse, messages, sendMessage]
2853
- );
2854
- const performResetSession = React.useCallback(() => {
2855
- resetToEmptyRef.current = true;
2856
- if (isRecording) stopRecording();
2857
- clearTranscript();
2858
- chatInputRef.current?.setDraft("");
2859
- resetChatSession();
2860
- callbacks.onResetSession?.();
2861
- }, [isRecording, stopRecording, clearTranscript, resetChatSession, callbacks]);
2862
- React.useImperativeHandle(
2863
- ref,
2864
- () => ({
2865
- resetSession: performResetSession,
2866
- clearMessages,
2867
- cancelStream,
2868
- getSessionId,
2869
- getMessages,
2870
- loadSession
2871
- }),
2872
- [performResetSession, clearMessages, cancelStream, getSessionId, getMessages, loadSession]
2873
- );
2874
- const ui = config.ui ?? {};
2875
- const theme = ui.theme === "dark" ? "dark" : "light";
2876
- const accent = ui.accent ?? DEFAULT_ACCENT;
2877
- const palette = React.useMemo(() => getPalette(theme, accent), [theme, accent]);
2878
- const placeholder = ui.input?.placeholder ?? "Type your message\u2026";
2879
- const showResetButton = ui.input?.showResetButton ?? false;
2880
- const messageActionsConfig = ui.messages?.actions;
2881
- const messageActions = React.useMemo(
2882
- () => ({
2883
- userMessageActions: {
2884
- copy: messageActionsConfig?.userMessageActions?.copy ?? true,
2885
- edit: messageActionsConfig?.userMessageActions?.edit ?? false,
2886
- retry: messageActionsConfig?.userMessageActions?.retry ?? false
2887
- },
2888
- assistantMessageActions: {
2889
- copy: messageActionsConfig?.assistantMessageActions?.copy ?? true,
2890
- trace: messageActionsConfig?.assistantMessageActions?.trace ?? true
2891
- }
2892
- }),
2893
- [messageActionsConfig]
2894
- );
2895
- const isEmpty = messages.length === 0;
2896
- const showEmptyShell = isEmpty && !hasEverSentMessage;
2897
- const emptyStateText = ui.emptyState?.text ?? "How may I assist you today?";
2898
- const emptyStateEyebrow = ui.emptyState?.eyebrow;
2899
- const showEmptyIcon = ui.emptyState?.icon ?? true;
2900
- const emptyStateLogo = ui.emptyState?.logo;
2901
- const suggestions = ui.emptyState?.suggestions;
2902
- const showSuggestions = suggestions != null && suggestions.enabled !== false && suggestions.categories.some((category) => category.prompts.length > 0);
2903
- const availability = config.availability;
2904
- if (availability?.state === "disabled") {
2905
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s7.root, { backgroundColor: palette.background }], children: [
2906
- children,
2907
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s7.disabledContainer, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.disabledText, { color: palette.disabledText }], children: "Chat is currently disabled" }) })
2908
- ] });
2909
- }
2910
- const isReadOnly = availability?.state === "readOnly";
2911
- const hideSendForVerification = userActionState.request != null && userActionState.result == null;
2912
- const verificationPanel = userActionState.request ? /* @__PURE__ */ jsxRuntime.jsx(
2913
- InlineVerificationPanel,
2914
- {
2915
- userActionRequest: userActionState.request,
2916
- clearOtpTrigger: userActionState.clearOtpTrigger,
2917
- onApprove: approveUserAction,
2918
- onReject: rejectUserAction,
2919
- onResend: resendOtp
2920
- }
2921
- ) : null;
2922
- const inputProps = {
2923
- onSend: handleSend,
2924
- onCancel: cancelStream,
2925
- disabled: false,
2926
- isStreaming: isWaitingForResponse,
2927
- placeholder: isRecording ? "Listening\u2026" : placeholder,
2928
- enableVoice: voiceEnabled,
2929
- voiceAvailable: voiceEnabled && voiceAvailable,
2930
- isRecording,
2931
- onVoicePress: voiceEnabled ? handleVoicePress : void 0,
2932
- showResetSession: showResetButton,
2933
- onResetSession: performResetSession,
2934
- showUploadImageButton: showUploadImage,
2935
- showAttachFileButton: showAttachFile,
2936
- onUploadImageClick: callbacks.onUploadImageClick,
2937
- onAttachFileClick: callbacks.onAttachFileClick,
2938
- theme,
2939
- hideSendButton: hideSendForVerification,
2940
- topAccessory: verificationPanel
2941
- };
2942
- return /* @__PURE__ */ jsxRuntime.jsxs(
2943
- reactNative.KeyboardAvoidingView,
2944
- {
2945
- style: [s7.root, { backgroundColor: palette.background }],
2946
- behavior: reactNative.Platform.OS === "ios" ? "padding" : "height",
2947
- keyboardVerticalOffset: 0,
2948
- children: [
2949
- children,
2950
- showEmptyShell ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s7.emptyStateRoot, children: [
2951
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s7.emptyStateCenter, onTouchStart: dismissKeyboard, children: [
2952
- emptyStateLogo ? emptyStateLogo : showEmptyIcon ? /* @__PURE__ */ jsxRuntime.jsx(HeroMark, { palette }) : null,
2953
- emptyStateEyebrow ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.emptyEyebrow, { color: palette.textMuted }], children: emptyStateEyebrow }) : null,
2954
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.emptyStateText, { color: "#94a3b8" }], children: emptyStateText }),
2955
- !emptyStateLogo && !showSuggestions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.emptyStateSubtext, { color: palette.textSecondary }], children: "Ask anything about your accounts, payments, or transfers." }) : null,
2956
- showSuggestions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s7.emptySuggestions, children: /* @__PURE__ */ jsxRuntime.jsx(
2957
- PromptSuggestionsV2,
2958
- {
2959
- categories: suggestions.categories,
2960
- disabled: isWaitingForResponse,
2961
- onPromptClick: handleSend
2962
- }
2963
- ) }) : null
2964
- ] }),
2965
- !isReadOnly ? /* @__PURE__ */ jsxRuntime.jsx(ChatInputV2, { ref: chatInputRef, ...inputProps }) : null
2966
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2967
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s7.messageListWrapper, onTouchStart: dismissKeyboard, children: /* @__PURE__ */ jsxRuntime.jsx(
2968
- MessageListV2,
2969
- {
2970
- ref: messageListRef,
2971
- messages,
2972
- isStreaming: isWaitingForResponse,
2973
- isLoadingSession: !!loadingSessionId,
2974
- onEditUserMessage: handleEditMessageDraft,
2975
- onRetryUserMessage: handleRetryUserMessage,
2976
- onExecutionTraceClick: callbacks.onExecutionTraceClick,
2977
- messageActions,
2978
- retryDisabled: isWaitingForResponse
2979
- }
2980
- ) }),
2981
- voiceEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
2982
- VoiceOverlay,
2983
- {
2984
- visible: isRecording,
2985
- voiceState,
2986
- transcribedText,
2987
- onStopRecording: stopRecording
2988
- }
2989
- ) : null,
2990
- !isReadOnly ? /* @__PURE__ */ jsxRuntime.jsx(
2991
- ChatInputV2,
2992
- {
2993
- ref: chatInputRef,
2994
- ...inputProps
2995
- }
2996
- ) : null
2997
- ] })
2998
- ]
2999
- }
3000
- );
3001
- }
3002
- );
3003
- function HeroMark({ palette }) {
3004
- const scale = React.useRef(new reactNative.Animated.Value(0.92)).current;
3005
- const opacity = React.useRef(new reactNative.Animated.Value(0)).current;
3006
- const ringSpin = React.useRef(new reactNative.Animated.Value(0)).current;
3007
- const dotPulse = React.useRef(new reactNative.Animated.Value(0)).current;
3008
- React.useEffect(() => {
3009
- reactNative.Animated.parallel([
3010
- reactNative.Animated.timing(opacity, {
3011
- toValue: 1,
3012
- duration: 420,
3013
- easing: reactNative.Easing.out(reactNative.Easing.cubic),
3014
- useNativeDriver: true
3015
- }),
3016
- reactNative.Animated.spring(scale, {
3017
- toValue: 1,
3018
- friction: 6,
3019
- tension: 90,
3020
- useNativeDriver: true
3021
- })
3022
- ]).start();
3023
- reactNative.Animated.loop(
3024
- reactNative.Animated.timing(ringSpin, {
3025
- toValue: 1,
3026
- duration: 14e3,
3027
- easing: reactNative.Easing.linear,
3028
- useNativeDriver: true
3029
- })
3030
- ).start();
3031
- reactNative.Animated.loop(
3032
- reactNative.Animated.sequence([
3033
- reactNative.Animated.timing(dotPulse, {
3034
- toValue: 1,
3035
- duration: 1600,
3036
- easing: reactNative.Easing.inOut(reactNative.Easing.cubic),
3037
- useNativeDriver: true
3038
- }),
3039
- reactNative.Animated.timing(dotPulse, {
3040
- toValue: 0,
3041
- duration: 1600,
3042
- easing: reactNative.Easing.inOut(reactNative.Easing.cubic),
3043
- useNativeDriver: true
3044
- })
3045
- ])
3046
- ).start();
3047
- }, [opacity, scale, ringSpin, dotPulse]);
3048
- const ringRotate = ringSpin.interpolate({
3049
- inputRange: [0, 1],
3050
- outputRange: ["0deg", "360deg"]
3051
- });
3052
- const dotScale = dotPulse.interpolate({
3053
- inputRange: [0, 1],
3054
- outputRange: [0.92, 1.06]
3055
- });
3056
- return /* @__PURE__ */ jsxRuntime.jsx(
3057
- reactNative.Animated.View,
3058
- {
3059
- style: [hero.halo, {
3060
- backgroundColor: palette.heroHaloFrom,
3061
- opacity,
3062
- transform: [{ scale }]
3063
- }],
3064
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [hero.haloInner, { backgroundColor: palette.heroHaloTo }], children: [
3065
- /* @__PURE__ */ jsxRuntime.jsx(
3066
- reactNative.Animated.View,
3067
- {
3068
- style: [
3069
- hero.ring,
3070
- { borderColor: palette.heroRing, transform: [{ rotate: ringRotate }] }
3071
- ]
3072
- }
3073
- ),
3074
- /* @__PURE__ */ jsxRuntime.jsx(
3075
- reactNative.Animated.View,
3076
- {
3077
- style: [
3078
- hero.dot,
3079
- { backgroundColor: palette.heroDot, transform: [{ scale: dotScale }] }
3080
- ]
3081
- }
3082
- )
3083
- ] })
3084
- }
3085
- );
3086
- }
3087
- var s7 = reactNative.StyleSheet.create({
3088
- root: {
3089
- flex: 1,
3090
- overflow: "hidden"
3091
- },
3092
- messageListWrapper: {
3093
- flex: 1,
3094
- minHeight: 0
3095
- },
3096
- emptyStateRoot: {
3097
- flex: 1,
3098
- justifyContent: "flex-end"
3099
- },
3100
- emptyStateCenter: {
3101
- flex: 1,
3102
- alignItems: "center",
3103
- justifyContent: "center",
3104
- paddingHorizontal: 32,
3105
- paddingBottom: 24
3106
- },
3107
- emptyEyebrow: {
3108
- marginTop: 28,
3109
- marginBottom: 8,
3110
- fontSize: 13,
3111
- fontWeight: "600",
3112
- letterSpacing: 0.4,
3113
- textTransform: "uppercase"
3114
- },
3115
- emptyStateText: {
3116
- fontSize: 30,
3117
- fontWeight: "600",
3118
- textAlign: "center",
3119
- letterSpacing: -0.7,
3120
- lineHeight: 36,
3121
- // Lean into a serif on iOS for a Claude-like editorial feel; system on Android
3122
- ...reactNative.Platform.select({
3123
- ios: { fontFamily: "Georgia" },
3124
- default: {}
3125
- }),
3126
- marginTop: 24
3127
- },
3128
- emptyStateSubtext: {
3129
- marginTop: 10,
3130
- fontSize: 15,
3131
- lineHeight: 22,
3132
- textAlign: "center",
3133
- letterSpacing: -0.15,
3134
- maxWidth: 320
3135
- },
3136
- emptySuggestions: {
3137
- width: "100%",
3138
- marginTop: 16
3139
- },
3140
- disabledContainer: {
3141
- flex: 1,
3142
- alignItems: "center",
3143
- justifyContent: "center",
3144
- padding: 16
3145
- },
3146
- disabledText: {
3147
- fontSize: 14,
3148
- textAlign: "center"
3149
- }
3150
- });
3151
- var hero = reactNative.StyleSheet.create({
3152
- halo: {
3153
- width: 96,
3154
- height: 96,
3155
- borderRadius: 48,
3156
- alignItems: "center",
3157
- justifyContent: "center"
3158
- },
3159
- haloInner: {
3160
- width: 76,
3161
- height: 76,
3162
- borderRadius: 38,
3163
- alignItems: "center",
3164
- justifyContent: "center"
3165
- },
3166
- ring: {
3167
- position: "absolute",
3168
- width: 60,
3169
- height: 60,
3170
- borderRadius: 30,
3171
- borderWidth: 1.5,
3172
- borderStyle: "dashed"
3173
- },
3174
- dot: {
3175
- width: 18,
3176
- height: 18,
3177
- borderRadius: 9
3178
- }
3179
- });
3180
- function cn(...inputs) {
3181
- return tailwindMerge.twMerge(clsx.clsx(inputs));
3182
- }
3183
- var DEFAULT_WIDTH = 260;
3184
- var DEFAULT_MIN_WIDTH = 220;
3185
- var DEFAULT_MAX_WIDTH = 440;
3186
- var DEFAULT_PAGE_SIZE = 20;
3187
- function getPersistKey(config, opts) {
3188
- if (opts.persistKey) return opts.persistKey;
3189
- return `payman-chat-sidebar:${config.workflow.id ?? "unknown"}`;
3190
- }
3191
- function loadPersistedState(key) {
3192
- if (typeof window === "undefined") return null;
3193
- try {
3194
- const raw = window.localStorage.getItem(key);
3195
- if (!raw) return null;
3196
- const parsed = JSON.parse(raw);
3197
- if (typeof parsed.width !== "number" || typeof parsed.collapsed !== "boolean") {
3198
- return null;
3199
- }
3200
- return { width: parsed.width, collapsed: parsed.collapsed };
3201
- } catch {
3202
- return null;
3203
- }
3204
- }
3205
- function savePersistedState(key, state) {
3206
- if (typeof window === "undefined") return;
3207
- try {
3208
- window.localStorage.setItem(key, JSON.stringify(state));
3209
- } catch {
3210
- }
3211
- }
3212
- function useSessionHistory(config, options = {}, optimisticActivity) {
3213
- const defaultWidth = options.defaultWidth ?? DEFAULT_WIDTH;
3214
- const minWidth = options.minWidth ?? DEFAULT_MIN_WIDTH;
3215
- const maxWidth = options.maxWidth ?? DEFAULT_MAX_WIDTH;
3216
- const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
3217
- const persistKey = React.useMemo(
3218
- () => getPersistKey(config, options),
3219
- [config.workflow.id, options.persistKey]
3220
- );
3221
- const [width, setWidthState] = React.useState(() => {
3222
- const persisted = loadPersistedState(persistKey);
3223
- return persisted?.width ?? defaultWidth;
3224
- });
3225
- const [collapsed, setCollapsedState] = React.useState(() => {
3226
- const persisted = loadPersistedState(persistKey);
3227
- return persisted?.collapsed ?? options.defaultCollapsed ?? false;
3228
- });
3229
- React.useEffect(() => {
3230
- const persisted = loadPersistedState(persistKey);
3231
- setWidthState(persisted?.width ?? defaultWidth);
3232
- setCollapsedState(persisted?.collapsed ?? options.defaultCollapsed ?? false);
3233
- }, [persistKey]);
3234
- const setWidth = React.useCallback(
3235
- (px) => {
3236
- const clamped = Math.max(minWidth, Math.min(maxWidth, Math.round(px)));
3237
- setWidthState(clamped);
3238
- savePersistedState(persistKey, { width: clamped, collapsed });
3239
- },
3240
- [collapsed, maxWidth, minWidth, persistKey]
3241
- );
3242
- const setCollapsed = React.useCallback(
3243
- (value) => {
3244
- setCollapsedState((prev) => {
3245
- const next = typeof value === "function" ? value(prev) : value;
3246
- savePersistedState(persistKey, { width, collapsed: next });
3247
- return next;
3248
- });
3249
- },
3250
- [persistKey, width]
3251
- );
3252
- const isReady = !!(config.session?.owner?.id && (config.workflow.id || config.workflow.name));
3253
- const [sessions, setSessions] = React.useState([]);
3254
- const [isLoading, setIsLoading] = React.useState(false);
3255
- const [error, setError] = React.useState(null);
3256
- const [page, setPage] = React.useState(0);
3257
- const [hasNext, setHasNext] = React.useState(false);
3258
- const inFlightRef = React.useRef(null);
3259
- const configRef = React.useRef(config);
3260
- configRef.current = config;
3261
- const fetchPage = React.useCallback(
3262
- async (pageIndex, replace) => {
3263
- if (!isReady) return;
3264
- inFlightRef.current?.abort();
3265
- const ac = new AbortController();
3266
- inFlightRef.current = ac;
3267
- setIsLoading(true);
3268
- setError(null);
3269
- try {
3270
- const resp = await paymanTypescriptAskSdk.listSessions(configRef.current, {
3271
- page: pageIndex,
3272
- size: pageSize,
3273
- signal: ac.signal
3274
- });
3275
- setSessions((prev) => replace ? resp.data : [...prev, ...resp.data]);
3276
- setPage(pageIndex);
3277
- setHasNext(resp.pageInfo.hasNext);
3278
- } catch (err) {
3279
- if (err.name !== "AbortError") {
3280
- setError(err);
3281
- }
3282
- } finally {
3283
- if (inFlightRef.current === ac) {
3284
- setIsLoading(false);
3285
- }
3286
- }
3287
- },
3288
- [isReady, pageSize]
3289
- );
3290
- const refresh = React.useCallback(() => fetchPage(0, true), [fetchPage]);
3291
- const loadMore = React.useCallback(async () => {
3292
- if (!hasNext || isLoading) return;
3293
- await fetchPage(page + 1, false);
3294
- }, [fetchPage, hasNext, isLoading, page]);
3295
- React.useEffect(() => {
3296
- if (!optimisticActivity) return;
3297
- setSessions((prev) => {
3298
- const next = [...prev];
3299
- let existingIndex = next.findIndex(
3300
- (session) => session.sessionId === optimisticActivity.sessionId
3301
- );
3302
- if (existingIndex === -1 && optimisticActivity.previousSessionId) {
3303
- existingIndex = next.findIndex(
3304
- (session) => session.sessionId === optimisticActivity.previousSessionId
3305
- );
3306
- }
3307
- const existing = existingIndex >= 0 ? next.splice(existingIndex, 1)[0] : void 0;
3308
- const preferredTitle = existing?.sessionTitle && existing.sessionTitle.trim() && existing.sessionTitle !== "Untitled session" ? existing.sessionTitle : optimisticActivity.sessionTitle?.trim() || existing?.sessionTitle || "Untitled session";
3309
- const localSession = existing ?? {
3310
- userId: config.session?.owner?.id ?? "",
3311
- userLabel: config.session?.owner?.name ?? config.session?.owner?.id ?? "",
3312
- sessionId: optimisticActivity.sessionId,
3313
- workflowId: config.workflow.id ?? "",
3314
- workflowName: config.workflow.name,
3315
- workflowVersion: config.workflow.version ?? 0,
3316
- lastMessageAt: optimisticActivity.lastMessageAt,
3317
- messageCount: 0,
3318
- sessionTitle: preferredTitle
3319
- };
3320
- next.unshift({
3321
- ...localSession,
3322
- sessionId: optimisticActivity.sessionId,
3323
- lastMessageAt: optimisticActivity.lastMessageAt,
3324
- sessionTitle: preferredTitle
3325
- });
3326
- return next;
3327
- });
3328
- }, [
3329
- config.session?.owner?.id,
3330
- config.session?.owner?.name,
3331
- config.workflow.id,
3332
- config.workflow.name,
3333
- config.workflow.version,
3334
- optimisticActivity
3335
- ]);
3336
- React.useEffect(() => {
3337
- if (!isReady) return;
3338
- void fetchPage(0, true);
3339
- return () => {
3340
- inFlightRef.current?.abort();
3341
- };
3342
- }, [
3343
- isReady,
3344
- config.workflow.id,
3345
- config.workflow.stage,
3346
- config.session?.owner?.id
3347
- ]);
3348
- return {
3349
- sessions,
3350
- isLoading,
3351
- error,
3352
- hasNext,
3353
- loadMore,
3354
- refresh,
3355
- width,
3356
- setWidth,
3357
- collapsed,
3358
- setCollapsed,
3359
- minWidth,
3360
- maxWidth,
3361
- isReady
3362
- };
3363
- }
3364
- var MOBILE_BREAKPOINT = 640;
3365
- function useIsMobile() {
3366
- const [isMobile, setIsMobile] = React.useState(() => {
3367
- if (typeof window === "undefined") return false;
3368
- return window.innerWidth < MOBILE_BREAKPOINT;
3369
- });
3370
- React.useEffect(() => {
3371
- if (typeof window === "undefined") return;
3372
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
3373
- const handler = (e) => setIsMobile(e.matches);
3374
- setIsMobile(mql.matches);
3375
- mql.addEventListener("change", handler);
3376
- return () => mql.removeEventListener("change", handler);
3377
- }, []);
3378
- return isMobile;
3379
- }
3380
- function readPortalThemeStyle(source) {
3381
- if (typeof window === "undefined" || !source) return {};
3382
- const root = source.closest(".payman-v2-root");
3383
- if (!(root instanceof HTMLElement)) return {};
3384
- const computed = window.getComputedStyle(root);
3385
- const style = {
3386
- color: computed.color,
3387
- fontFamily: computed.fontFamily,
3388
- fontSize: computed.fontSize,
3389
- lineHeight: computed.lineHeight
3390
- };
3391
- for (let index = 0; index < computed.length; index += 1) {
3392
- const name = computed.item(index);
3393
- if (!name.startsWith("--payman-v2-")) continue;
3394
- const value = computed.getPropertyValue(name).trim();
3395
- if (value) style[name] = value;
3396
- }
3397
- return style;
3398
- }
3399
- function SessionHistorySidebar({
3400
- config,
3401
- options,
3402
- activeSessionId,
3403
- loadingSessionId,
3404
- recentlyCompletedSessionIds,
3405
- optimisticActivity,
3406
- onSelectSession,
3407
- onNewSession,
3408
- newSessionDisabled = false,
3409
- mobileOpen,
3410
- onMobileOpenChange
3411
- }) {
3412
- const isMobile = useIsMobile();
3413
- const mobileThemeSourceRef = React.useRef(null);
3414
- const [mobilePortalThemeStyle, setMobilePortalThemeStyle] = React.useState({});
3415
- const history = useSessionHistory(config, options, optimisticActivity);
3416
- const showNewSessionButton = options.showNewSessionButton === true;
3417
- React.useEffect(() => {
3418
- if (!isMobile) return;
3419
- setMobilePortalThemeStyle(readPortalThemeStyle(mobileThemeSourceRef.current));
3420
- }, [isMobile, mobileOpen]);
3421
- if (isMobile) {
3422
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: mobileThemeSourceRef, style: { display: "contents" }, children: [
3423
- !mobileOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-mobile-trigger", children: /* @__PURE__ */ jsxRuntime.jsx(
3424
- "button",
3425
- {
3426
- type: "button",
3427
- "aria-label": "Open recent sessions",
3428
- title: "Recent sessions",
3429
- onClick: () => onMobileOpenChange(true),
3430
- className: "payman-sidebar-collapsed-button payman-sidebar-mobile-trigger-button",
3431
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelBottomOpen, { size: 17 })
3432
- }
3433
- ) }),
3434
- /* @__PURE__ */ jsxRuntime.jsx(
3435
- MobileDrawer,
3436
- {
3437
- open: mobileOpen,
3438
- onOpenChange: onMobileOpenChange,
3439
- themeStyle: mobilePortalThemeStyle,
3440
- history,
3441
- activeSessionId,
3442
- loadingSessionId,
3443
- recentlyCompletedSessionIds,
3444
- showNewSessionButton,
3445
- onNewSession,
3446
- newSessionDisabled,
3447
- onSelectSession: (s9) => {
3448
- onSelectSession(s9);
3449
- onMobileOpenChange(false);
3450
- }
3451
- }
3452
- )
3453
- ] });
3454
- }
3455
- return /* @__PURE__ */ jsxRuntime.jsx(
3456
- DesktopSidebar,
3457
- {
3458
- history,
3459
- activeSessionId,
3460
- loadingSessionId,
3461
- recentlyCompletedSessionIds,
3462
- showNewSessionButton,
3463
- onNewSession,
3464
- newSessionDisabled,
3465
- onSelectSession
3466
- }
3467
- );
3468
- }
3469
- function DesktopSidebar({
3470
- history,
3471
- activeSessionId,
3472
- loadingSessionId,
3473
- recentlyCompletedSessionIds,
3474
- showNewSessionButton,
3475
- onNewSession,
3476
- newSessionDisabled,
3477
- onSelectSession
3478
- }) {
3479
- const { width, setWidth, collapsed, setCollapsed, minWidth, maxWidth } = history;
3480
- const reduceMotion = framerMotion.useReducedMotion();
3481
- const dragStateRef = React.useRef(null);
3482
- const [isDragging, setIsDragging] = React.useState(false);
3483
- const [handleHover, setHandleHover] = React.useState(false);
3484
- const onResizeStart = React.useCallback(
3485
- (e) => {
3486
- e.preventDefault();
3487
- dragStateRef.current = { startX: e.clientX, startWidth: width };
3488
- setIsDragging(true);
3489
- },
3490
- [width]
3491
- );
3492
- React.useEffect(() => {
3493
- if (!isDragging) return;
3494
- const onMove = (e) => {
3495
- const st = dragStateRef.current;
3496
- if (!st) return;
3497
- setWidth(st.startWidth + (e.clientX - st.startX));
3498
- };
3499
- const onEnd = () => {
3500
- dragStateRef.current = null;
3501
- setIsDragging(false);
3502
- };
3503
- window.addEventListener("pointermove", onMove);
3504
- window.addEventListener("pointerup", onEnd);
3505
- window.addEventListener("pointercancel", onEnd);
3506
- return () => {
3507
- window.removeEventListener("pointermove", onMove);
3508
- window.removeEventListener("pointerup", onEnd);
3509
- window.removeEventListener("pointercancel", onEnd);
3510
- };
3511
- }, [isDragging, setWidth]);
3512
- const handleActive = handleHover || isDragging;
3513
- const sidebarEnter = reduceMotion ? { duration: 0.1, ease: "easeOut" } : { duration: 0.16, ease: [0.25, 0.46, 0.45, 0.94] };
3514
- if (collapsed) {
3515
- return /* @__PURE__ */ jsxRuntime.jsx(
3516
- framerMotion.motion.div,
3517
- {
3518
- className: "payman-sidebar-collapsed-mount",
3519
- initial: reduceMotion ? false : { opacity: 0, scale: 0.96 },
3520
- animate: { opacity: 1, scale: 1 },
3521
- transition: sidebarEnter,
3522
- style: { alignSelf: "flex-start" },
3523
- children: /* @__PURE__ */ jsxRuntime.jsx(
3524
- CollapsedButton,
3525
- {
3526
- onExpand: () => setCollapsed(false),
3527
- history,
3528
- activeSessionId,
3529
- loadingSessionId,
3530
- recentlyCompletedSessionIds,
3531
- showNewSessionButton,
3532
- onNewSession,
3533
- newSessionDisabled,
3534
- onSelectSession
3535
- }
3536
- )
3537
- },
3538
- "payman-sidebar-desktop-collapsed"
3539
- );
3540
- }
3541
- return /* @__PURE__ */ jsxRuntime.jsxs(
3542
- framerMotion.motion.aside,
3543
- {
3544
- className: "payman-sidebar payman-sidebar-desktop",
3545
- initial: reduceMotion ? false : { opacity: 0 },
3546
- animate: { opacity: 1 },
3547
- transition: sidebarEnter,
3548
- style: {
3549
- width,
3550
- minWidth,
3551
- maxWidth,
3552
- height: "100%",
3553
- minHeight: 0,
3554
- alignSelf: "stretch",
3555
- display: "flex",
3556
- flexDirection: "column",
3557
- overflow: "hidden",
3558
- transition: isDragging ? "none" : "width 100ms ease"
3559
- },
3560
- children: [
3561
- /* @__PURE__ */ jsxRuntime.jsx(
3562
- SidebarTopBar,
3563
- {
3564
- onCollapse: () => setCollapsed(true),
3565
- showNewSessionButton,
3566
- onNewSession,
3567
- newSessionDisabled
3568
- }
3569
- ),
3570
- /* @__PURE__ */ jsxRuntime.jsx(
3571
- SessionList,
3572
- {
3573
- history,
3574
- activeSessionId,
3575
- loadingSessionId,
3576
- recentlyCompletedSessionIds,
3577
- onSelectSession
3578
- }
3579
- ),
3580
- /* @__PURE__ */ jsxRuntime.jsx(
3581
- "div",
3582
- {
3583
- role: "separator",
3584
- "aria-orientation": "vertical",
3585
- onPointerDown: onResizeStart,
3586
- onMouseEnter: () => setHandleHover(true),
3587
- onMouseLeave: () => setHandleHover(false),
3588
- className: "payman-sidebar-resize",
3589
- style: {
3590
- position: "absolute",
3591
- top: 0,
3592
- right: -4,
3593
- width: 8,
3594
- height: "100%",
3595
- cursor: "col-resize",
3596
- userSelect: "none",
3597
- touchAction: "none",
3598
- zIndex: 2,
3599
- display: "flex",
3600
- alignItems: "center",
3601
- justifyContent: "center"
3602
- },
3603
- children: /* @__PURE__ */ jsxRuntime.jsx(
3604
- "span",
3605
- {
3606
- "aria-hidden": true,
3607
- style: {
3608
- width: 3,
3609
- height: 36,
3610
- borderRadius: 999,
3611
- background: handleActive ? "var(--payman-v2-text-3)" : "transparent",
3612
- opacity: handleActive ? 1 : 0,
3613
- transition: "opacity 100ms ease, background 100ms ease"
3614
- }
3615
- }
3616
- )
3617
- }
3618
- )
3619
- ]
3620
- },
3621
- "payman-sidebar-desktop-expanded"
3622
- );
3623
- }
3624
- var POPOVER_LEAVE_DELAY_MS = 220;
3625
- function CollapsedButton({
3626
- onExpand,
3627
- history,
3628
- activeSessionId,
3629
- loadingSessionId,
3630
- recentlyCompletedSessionIds,
3631
- showNewSessionButton,
3632
- onNewSession,
3633
- newSessionDisabled,
3634
- onSelectSession
3635
- }) {
3636
- const reduceMotion = framerMotion.useReducedMotion();
3637
- const [hoverOpen, setHoverOpen] = React.useState(false);
3638
- const leaveTimerRef = React.useRef(null);
3639
- const openPopover = () => {
3640
- if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
3641
- setHoverOpen(true);
3642
- };
3643
- const schedulePopoverClose = () => {
3644
- if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
3645
- leaveTimerRef.current = setTimeout(
3646
- () => setHoverOpen(false),
3647
- POPOVER_LEAVE_DELAY_MS
3648
- );
3649
- };
3650
- React.useEffect(
3651
- () => () => {
3652
- if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
3653
- },
3654
- []
3655
- );
3656
- return /* @__PURE__ */ jsxRuntime.jsxs(
3657
- "div",
3658
- {
3659
- className: "payman-sidebar-collapsed",
3660
- onMouseEnter: openPopover,
3661
- onMouseLeave: schedulePopoverClose,
3662
- style: { position: "relative", alignSelf: "flex-start" },
3663
- children: [
3664
- /* @__PURE__ */ jsxRuntime.jsx(
3665
- framerMotion.motion.button,
3666
- {
3667
- type: "button",
3668
- "aria-label": "Expand sidebar",
3669
- title: "Expand sidebar",
3670
- onClick: onExpand,
3671
- className: "payman-sidebar-collapsed-button",
3672
- whileHover: reduceMotion ? void 0 : { scale: 1.04 },
3673
- whileTap: reduceMotion ? void 0 : { scale: 0.97 },
3674
- transition: { type: "spring", stiffness: 620, damping: 32 },
3675
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 16 })
3676
- }
3677
- ),
3678
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: hoverOpen && /* @__PURE__ */ jsxRuntime.jsxs(
3679
- framerMotion.motion.div,
3680
- {
3681
- className: "payman-sidebar-popover",
3682
- onMouseEnter: openPopover,
3683
- onMouseLeave: schedulePopoverClose,
3684
- initial: reduceMotion ? { opacity: 0 } : { opacity: 0, x: -6, scale: 0.98 },
3685
- animate: reduceMotion ? { opacity: 1 } : { opacity: 1, x: 0, scale: 1 },
3686
- exit: reduceMotion ? { opacity: 0 } : { opacity: 0, x: -4, scale: 0.99 },
3687
- transition: reduceMotion ? { duration: 0.08 } : { duration: 0.14, ease: [0.25, 0.46, 0.45, 0.94] },
3688
- style: {
3689
- position: "absolute",
3690
- left: "calc(100% + 6px)",
3691
- top: 6,
3692
- width: Math.min(history.width, 320),
3693
- maxHeight: 480,
3694
- zIndex: 50,
3695
- display: "flex",
3696
- flexDirection: "column",
3697
- overflow: "hidden",
3698
- transformOrigin: "left center"
3699
- },
3700
- children: [
3701
- /* @__PURE__ */ jsxRuntime.jsx(
3702
- SidebarTopBar,
3703
- {
3704
- showNewSessionButton,
3705
- onNewSession,
3706
- newSessionDisabled
3707
- }
3708
- ),
3709
- /* @__PURE__ */ jsxRuntime.jsx(
3710
- SessionList,
3711
- {
3712
- history,
3713
- activeSessionId,
3714
- loadingSessionId,
3715
- recentlyCompletedSessionIds,
3716
- onSelectSession: (s9) => {
3717
- onSelectSession(s9);
3718
- setHoverOpen(false);
3719
- }
3720
- }
3721
- )
3722
- ]
3723
- },
3724
- "payman-sidebar-hover-popover"
3725
- ) })
3726
- ]
3727
- }
3728
- );
3729
- }
3730
- function MobileDrawer({
3731
- open,
3732
- onOpenChange,
3733
- themeStyle,
3734
- history,
3735
- activeSessionId,
3736
- loadingSessionId,
3737
- recentlyCompletedSessionIds,
3738
- showNewSessionButton,
3739
- onNewSession,
3740
- newSessionDisabled,
3741
- onSelectSession
3742
- }) {
3743
- const handleNewSession = onNewSession ? () => {
3744
- onOpenChange(false);
3745
- onNewSession();
3746
- } : void 0;
3747
- if (!open || typeof document === "undefined") return null;
3748
- return reactDom.createPortal(
3749
- /* @__PURE__ */ jsxRuntime.jsx(
3750
- framerMotion.motion.div,
3751
- {
3752
- className: "payman-sidebar-backdrop",
3753
- onClick: () => onOpenChange(false),
3754
- style: themeStyle,
3755
- initial: { opacity: 0 },
3756
- animate: { opacity: 1 },
3757
- transition: { duration: 0.18, ease: "easeOut" },
3758
- children: /* @__PURE__ */ jsxRuntime.jsxs(
3759
- framerMotion.motion.aside,
3760
- {
3761
- className: "payman-sidebar payman-sidebar-sheet",
3762
- "data-open": "true",
3763
- onClick: (e) => e.stopPropagation(),
3764
- role: "dialog",
3765
- "aria-modal": "true",
3766
- "aria-label": "Recent sessions",
3767
- initial: { y: 28, opacity: 0.96 },
3768
- animate: { y: 0, opacity: 1 },
3769
- transition: {
3770
- type: "spring",
3771
- stiffness: 380,
3772
- damping: 32,
3773
- mass: 0.92
3774
- },
3775
- children: [
3776
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-sheet-grabber", "aria-hidden": true }),
3777
- /* @__PURE__ */ jsxRuntime.jsx(
3778
- SidebarTopBar,
3779
- {
3780
- onCollapse: () => onOpenChange(false),
3781
- mobile: true,
3782
- showNewSessionButton,
3783
- onNewSession: handleNewSession,
3784
- newSessionDisabled
3785
- }
3786
- ),
3787
- /* @__PURE__ */ jsxRuntime.jsx(
3788
- SessionList,
3789
- {
3790
- history,
3791
- activeSessionId,
3792
- loadingSessionId,
3793
- recentlyCompletedSessionIds,
3794
- onSelectSession
3795
- }
3796
- )
3797
- ]
3798
- }
3799
- )
3800
- }
3801
- ),
3802
- document.body
3803
- );
3804
- }
3805
- function SidebarTopBar({
3806
- onCollapse,
3807
- mobile = false,
3808
- showNewSessionButton = false,
3809
- onNewSession,
3810
- newSessionDisabled = false
3811
- }) {
3812
- const shouldShowNewSession = showNewSessionButton && onNewSession != null;
3813
- return /* @__PURE__ */ jsxRuntime.jsxs(
3814
- "div",
3815
- {
3816
- className: cn(
3817
- "payman-sidebar-topbar",
3818
- shouldShowNewSession && "has-new-session"
3819
- ),
3820
- children: [
3821
- /* @__PURE__ */ jsxRuntime.jsx(SidebarHeader, { onCollapse, mobile }),
3822
- shouldShowNewSession && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-new-session-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs(
3823
- "button",
3824
- {
3825
- type: "button",
3826
- className: "payman-sidebar-new-session-button",
3827
- onClick: onNewSession,
3828
- disabled: newSessionDisabled,
3829
- "aria-label": "New Session",
3830
- title: "New Session",
3831
- children: [
3832
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 13, strokeWidth: 2.4 }),
3833
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "New Session" })
3834
- ]
3835
- }
3836
- ) })
3837
- ]
3838
- }
3839
- );
3840
- }
3841
- function SidebarHeader({
3842
- onCollapse,
3843
- mobile = false
3844
- }) {
3845
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-sidebar-header", children: [
3846
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-sidebar-header-title", children: "Recent sessions" }),
3847
- onCollapse && /* @__PURE__ */ jsxRuntime.jsx(
3848
- "button",
3849
- {
3850
- type: "button",
3851
- "aria-label": mobile ? "Close recent sessions" : "Collapse sidebar",
3852
- title: mobile ? "Close recent sessions" : "Collapse sidebar",
3853
- onClick: onCollapse,
3854
- className: "payman-sidebar-header-icon-button",
3855
- children: mobile ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 15 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftClose, { size: 13 })
3856
- }
3857
- )
3858
- ] });
3859
- }
3860
- function SessionList({
3861
- history,
3862
- activeSessionId,
3863
- loadingSessionId,
3864
- recentlyCompletedSessionIds,
3865
- onSelectSession
3866
- }) {
3867
- const listRef = React.useRef(null);
3868
- const { sessions, hasNext, isLoading, error, loadMore, isReady, refresh } = history;
3869
- const formattedNow = React.useMemo(() => /* @__PURE__ */ new Date(), []);
3870
- const sessionGroups = React.useMemo(
3871
- () => groupSessionsByDate(sessions, formattedNow),
3872
- [sessions, formattedNow]
3873
- );
3874
- if (!isReady) {
3875
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-notice-shell", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-notice", children: "Configure the chat settings." }) });
3876
- }
3877
- return /* @__PURE__ */ jsxRuntime.jsxs(
3878
- "div",
3879
- {
3880
- ref: listRef,
3881
- className: "payman-sidebar-list",
3882
- style: { flex: "1 1 0%", minHeight: 0, overflowY: "auto" },
3883
- children: [
3884
- error && sessions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { onRetry: () => void refresh() }),
3885
- !error && sessions.length === 0 && isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-loading", children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 18 }) }),
3886
- !error && sessions.length === 0 && !isLoading && /* @__PURE__ */ jsxRuntime.jsx(EmptyState, {}),
3887
- sessionGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(
3888
- "section",
3889
- {
3890
- className: "payman-sidebar-group",
3891
- "aria-label": group.label,
3892
- children: [
3893
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-group-label", children: group.label }),
3894
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-group-items", children: group.sessions.map((s9) => /* @__PURE__ */ jsxRuntime.jsx(
3895
- SessionRow,
3896
- {
3897
- session: s9,
3898
- isActive: s9.sessionId === activeSessionId,
3899
- isLoading: s9.sessionId === loadingSessionId,
3900
- isRecentlyCompleted: recentlyCompletedSessionIds?.has(s9.sessionId) ?? false,
3901
- onSelect: onSelectSession
3902
- },
3903
- s9.sessionId
3904
- )) })
3905
- ]
3906
- },
3907
- group.key
3908
- )),
3909
- sessions.length > 0 && hasNext && /* @__PURE__ */ jsxRuntime.jsx(
3910
- LoadMoreButton,
3911
- {
3912
- isLoading,
3913
- onClick: () => void loadMore()
3914
- }
3915
- ),
3916
- sessions.length > 0 && error && /* @__PURE__ */ jsxRuntime.jsx(
3917
- LoadMoreButton,
3918
- {
3919
- isLoading: false,
3920
- label: "Retry",
3921
- onClick: () => void refresh()
3922
- }
3923
- )
3924
- ]
3925
- }
3926
- );
3927
- }
3928
- function SessionRow({
3929
- session,
3930
- isActive,
3931
- isLoading,
3932
- isRecentlyCompleted,
3933
- onSelect
3934
- }) {
3935
- let statusNode = null;
3936
- let statusLabel;
3937
- if (isRecentlyCompleted) {
3938
- statusNode = /* @__PURE__ */ jsxRuntime.jsx(
3939
- "span",
3940
- {
3941
- className: "payman-sidebar-row-status payman-sidebar-row-status-done",
3942
- "aria-hidden": true,
3943
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 11, strokeWidth: 2.5 })
3944
- }
3945
- );
3946
- statusLabel = "Just finished";
3947
- } else if (isLoading) {
3948
- statusNode = /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "payman-sidebar-row-spinner" });
3949
- statusLabel = "Loading";
3950
- }
3951
- return /* @__PURE__ */ jsxRuntime.jsx(
3952
- "button",
3953
- {
3954
- type: "button",
3955
- onClick: () => onSelect(session),
3956
- className: cn("payman-sidebar-row", isActive && "is-active"),
3957
- title: statusLabel ? `${session.sessionTitle || "Untitled session"} \u2014 ${statusLabel}` : session.sessionTitle,
3958
- "aria-busy": isLoading || void 0,
3959
- children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "payman-sidebar-row-main", children: [
3960
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-sidebar-row-title", children: session.sessionTitle || "Untitled session" }),
3961
- statusNode
3962
- ] })
3963
- }
3964
- );
3965
- }
3966
- function EmptyState() {
3967
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-sidebar-empty", children: [
3968
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-empty-icon", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { size: 18 }) }),
3969
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-empty-title", children: "No sessions yet" }),
3970
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-empty-desc", children: "Your conversations will appear here once you start chatting." })
3971
- ] });
3972
- }
3973
- function ErrorState({ onRetry }) {
3974
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-sidebar-error", children: [
3975
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-error-icon", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { size: 18 }) }),
3976
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-error-title", children: "Couldn't load sessions" }),
3977
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-error-desc", children: "Check your connection and try again." }),
3978
- /* @__PURE__ */ jsxRuntime.jsx(
3979
- LoadMoreButton,
3980
- {
3981
- isLoading: false,
3982
- label: "Try again",
3983
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 12 }),
3984
- onClick: onRetry
3985
- }
3986
- )
3987
- ] });
3988
- }
3989
- function LoadMoreButton({
3990
- isLoading,
3991
- onClick,
3992
- label,
3993
- icon
3994
- }) {
3995
- return /* @__PURE__ */ jsxRuntime.jsx(
3996
- "button",
3997
- {
3998
- type: "button",
3999
- onClick,
4000
- disabled: isLoading,
4001
- className: "payman-sidebar-load-more",
4002
- children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4003
- icon,
4004
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: label ?? "Load more sessions" })
4005
- ] })
4006
- }
4007
- );
4008
- }
4009
- function Spinner({ size = 12 }) {
4010
- return /* @__PURE__ */ jsxRuntime.jsx(
4011
- "span",
4012
- {
4013
- "aria-hidden": true,
4014
- className: "payman-sidebar-spinner",
4015
- style: {
4016
- width: size,
4017
- height: size,
4018
- borderRadius: "50%",
4019
- border: `${Math.max(1.5, size / 8)}px solid var(--payman-v2-border-1)`,
4020
- borderTopColor: "var(--payman-v2-text-2)",
4021
- display: "inline-block",
4022
- animation: "payman-v2-spin 0.7s linear infinite"
4023
- }
4024
- }
4025
- );
4026
- }
4027
- function groupSessionsByDate(sessions, now) {
4028
- const groups = /* @__PURE__ */ new Map();
4029
- const orderedGroups = [];
4030
- for (const session of sessions) {
4031
- const bucket = getSessionDateBucket(session.lastMessageAt, now);
4032
- const existing = groups.get(bucket.key);
4033
- if (existing) {
4034
- existing.sessions.push(session);
4035
- continue;
4036
- }
4037
- const group = {
4038
- key: bucket.key,
4039
- label: bucket.label,
4040
- sessions: [session]
4041
- };
4042
- groups.set(bucket.key, group);
4043
- orderedGroups.push(group);
4044
- }
4045
- return orderedGroups;
4046
- }
4047
- function titleCaseDateHeading(label) {
4048
- return label.replace(/[\p{L}\p{M}]+/gu, (word) => {
4049
- const lower = word.toLocaleLowerCase(void 0);
4050
- const first = lower.charAt(0).toLocaleUpperCase(void 0);
4051
- return first + lower.slice(1);
4052
- });
4053
- }
4054
- function getSessionDateBucket(iso, now) {
4055
- const date = new Date(iso);
4056
- if (Number.isNaN(date.getTime())) {
4057
- return { key: "older", label: titleCaseDateHeading("Older") };
4058
- }
4059
- const dayKey = [
4060
- date.getFullYear(),
4061
- String(date.getMonth() + 1).padStart(2, "0"),
4062
- String(date.getDate()).padStart(2, "0")
4063
- ].join("-");
4064
- const sessionDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
4065
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
4066
- const dayDiff = Math.round(
4067
- (today.getTime() - sessionDay.getTime()) / 864e5
4068
- );
4069
- if (dayDiff <= 0) return { key: dayKey, label: titleCaseDateHeading("Today") };
4070
- if (dayDiff === 1) return { key: dayKey, label: titleCaseDateHeading("Yesterday") };
4071
- if (dayDiff < 7) {
4072
- return {
4073
- key: dayKey,
4074
- label: titleCaseDateHeading(
4075
- date.toLocaleDateString(void 0, { weekday: "long" })
4076
- )
4077
- };
4078
- }
4079
- const sameYear = date.getFullYear() === now.getFullYear();
4080
- return {
4081
- key: dayKey,
4082
- label: titleCaseDateHeading(
4083
- date.toLocaleDateString(void 0, {
4084
- month: "long",
4085
- day: "numeric",
4086
- year: sameYear ? void 0 : "numeric"
4087
- })
4088
- )
4089
- };
4090
- }
4091
- function ChatHeader({
4092
- sessionId,
4093
- onCopySessionId,
4094
- onNewSession,
4095
- showResetSession = false,
4096
- children
4097
- }) {
4098
- const [copiedSessionId, setCopiedSessionId] = React.useState(false);
4099
- const handleCopySessionId = () => {
4100
- if (sessionId && onCopySessionId) {
4101
- reactNative.Clipboard.setString(sessionId);
4102
- setCopiedSessionId(true);
4103
- setTimeout(() => setCopiedSessionId(false), 2e3);
4104
- onCopySessionId(sessionId);
4105
- }
4106
- };
4107
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.container, children: [
4108
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.left, children: [
4109
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.title, children: "Chat" }),
4110
- sessionId && /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress: handleCopySessionId, style: styles3.sessionChip, children: [
4111
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.sessionText, children: sessionId.length > 20 ? `${sessionId.substring(0, 8)}\u2026${sessionId.slice(-8)}` : sessionId }),
4112
- copiedSessionId ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.copyHint, children: "copied" }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.copyHint, children: "copy" })
4113
- ] })
4114
- ] }),
4115
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.right, children: [
4116
- showResetSession && onNewSession && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onNewSession, style: styles3.btn, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.btnText, children: "Reset" }) }),
4117
- children
4118
- ] })
4119
- ] });
4120
- }
4121
- var styles3 = reactNative.StyleSheet.create({
4122
- container: {
4123
- flexDirection: "row",
4124
- alignItems: "center",
4125
- justifyContent: "space-between",
4126
- paddingHorizontal: 16,
4127
- height: 56,
4128
- borderBottomWidth: 1,
4129
- borderBottomColor: "rgba(0,0,0,0.08)",
4130
- backgroundColor: "rgba(255,255,255,0.85)"
4131
- },
4132
- left: { flex: 1, minWidth: 0 },
4133
- right: { flexDirection: "row", alignItems: "center", gap: 6 },
4134
- title: { fontSize: 14, fontWeight: "600", color: "#111" },
4135
- sessionChip: {
4136
- flexDirection: "row",
4137
- alignItems: "center",
4138
- gap: 4,
4139
- marginTop: 2
4140
- },
4141
- sessionText: { fontSize: 10, color: "#666", fontFamily: "Menlo" },
4142
- copyHint: { fontSize: 9, color: "#999" },
4143
- btn: {
4144
- paddingHorizontal: 10,
4145
- paddingVertical: 6,
4146
- borderRadius: 8,
4147
- borderWidth: 1,
4148
- borderColor: "rgba(0,0,0,0.12)"
4149
- },
4150
- btnText: { fontSize: 12, color: "#555" }
4151
- });
4152
-
4153
- // src/assets/payman-mono-crop-blue.png
4154
- var payman_mono_crop_blue_default = "data:image/png;base64,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";
4155
- var PAYMAN_BRAND_GREEN = "#0A3B44";
4156
- var PAYMAN = {
4157
- mutedForeground: "#71717a",
4158
- border: "#e4e4e7",
4159
- card: "#ffffff",
4160
- modalBackdrop: "rgba(0,0,0,0.4)"
4161
- };
4162
- var { width: SCREEN_WIDTH } = reactNative.Dimensions.get("window");
4163
- var DIALOG_MAX_WIDTH = Math.min(440, SCREEN_WIDTH * 0.94);
4164
- var DIALOG_PADDING = 28;
4165
- var OTP_ERROR_FLASH_MS2 = 600;
4166
- function UserActionModal({
4167
- isOpen,
4168
- userActionRequest,
4169
- onApprove,
4170
- onReject,
4171
- onResend,
4172
- clearOtpTrigger
4173
- }) {
4174
- const [otp, setOtp] = React.useState("");
4175
- const [actionType, setActionType] = React.useState(null);
4176
- const [isSubmitting, setIsSubmitting] = React.useState(false);
4177
- const [resendCooldownRemaining, setResendCooldownRemaining] = React.useState(0);
4178
- const [keyboardVisible, setKeyboardVisible] = React.useState(false);
4179
- const [otpError, setOtpError] = React.useState(false);
4180
- const lastAutoSubmittedRef = React.useRef("");
4181
- const submitInFlightRef = React.useRef(false);
4182
- const submitGenerationRef = React.useRef(0);
4183
- const schema = getOtpSchemaFromRequest(userActionRequest?.requestedSchema);
4184
- React.useEffect(() => {
4185
- const show = reactNative.Keyboard.addListener(
4186
- reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
4187
- () => setKeyboardVisible(true)
4188
- );
4189
- const hide = reactNative.Keyboard.addListener(
4190
- reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
4191
- () => setKeyboardVisible(false)
4192
- );
4193
- return () => {
4194
- show.remove();
4195
- hide.remove();
4196
- };
4197
- }, []);
4198
- const resetActionState = React.useCallback(() => {
4199
- setIsSubmitting(false);
4200
- setActionType(null);
4201
- }, []);
4202
- React.useEffect(() => {
4203
- if (isOpen) {
4204
- setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
4205
- } else {
4206
- setOtp("");
4207
- resetActionState();
4208
- setResendCooldownRemaining(0);
4209
- setOtpError(false);
4210
- lastAutoSubmittedRef.current = "";
4211
- submitInFlightRef.current = false;
4212
- submitGenerationRef.current += 1;
4213
- }
4214
- }, [isOpen, resetActionState]);
4215
- React.useEffect(() => {
4216
- if (resendCooldownRemaining <= 0) return;
4217
- const timer = setTimeout(
4218
- () => setResendCooldownRemaining((prev) => prev - 1),
4219
- 1e3
4220
- );
4221
- return () => clearTimeout(timer);
4222
- }, [resendCooldownRemaining]);
4223
- React.useEffect(() => {
4224
- if (clearOtpTrigger > 0) {
4225
- setOtpError(true);
4226
- const t = setTimeout(() => {
4227
- setOtpError(false);
4228
- setOtp("");
4229
- resetActionState();
4230
- }, OTP_ERROR_FLASH_MS2);
4231
- return () => clearTimeout(t);
4232
- }
4233
- }, [clearOtpTrigger, resetActionState]);
4234
- React.useEffect(() => {
4235
- if (!isOpen || !isSubmitting) return;
4236
- if (actionType !== "approve" && actionType !== "reject") return;
4237
- const timeout = setTimeout(
4238
- () => resetActionState(),
4239
- ACTION_PENDING_TIMEOUT_MS
4240
- );
4241
- return () => clearTimeout(timeout);
4242
- }, [isOpen, isSubmitting, actionType, resetActionState]);
4243
- React.useEffect(() => {
4244
- if (!isOpen || !userActionRequest) return;
4245
- if (otp.length !== schema.maxLength || !/^\d+$/.test(otp)) {
4246
- return;
4247
- }
4248
- if (isSubmitting || submitInFlightRef.current) return;
4249
- if (lastAutoSubmittedRef.current === otp) return;
4250
- lastAutoSubmittedRef.current = otp;
4251
- submitInFlightRef.current = true;
4252
- const submitGeneration = submitGenerationRef.current;
4253
- void (async () => {
4254
- setIsSubmitting(true);
4255
- setActionType("approve");
4256
- try {
4257
- await onApprove(otp);
4258
- } catch {
4259
- if (submitGenerationRef.current !== submitGeneration) return;
4260
- resetActionState();
4261
- lastAutoSubmittedRef.current = otp;
4262
- } finally {
4263
- if (submitGenerationRef.current !== submitGeneration) return;
4264
- submitInFlightRef.current = false;
4265
- }
4266
- })();
4267
- }, [
4268
- otp,
4269
- isOpen,
4270
- isSubmitting,
4271
- userActionRequest,
4272
- schema.maxLength,
4273
- onApprove,
4274
- resetActionState
4275
- ]);
4276
- const handleReject = React.useCallback(async () => {
4277
- setIsSubmitting(true);
4278
- setActionType("reject");
4279
- try {
4280
- await onReject();
4281
- } catch {
4282
- resetActionState();
4283
- }
4284
- }, [onReject, resetActionState]);
4285
- const handleResend = React.useCallback(async () => {
4286
- if (resendCooldownRemaining > 0) return;
4287
- setIsSubmitting(true);
4288
- setActionType("resend");
4289
- try {
4290
- await onResend();
4291
- setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
4292
- } catch {
4293
- } finally {
4294
- setActionType(null);
4295
- setIsSubmitting(false);
4296
- }
4297
- }, [resendCooldownRemaining, onResend]);
4298
- if (!userActionRequest) return null;
4299
- const isPayment = userActionRequest.userActionType === "PAYMENT_APPROVAL" && Boolean(userActionRequest.metadata?.amount);
4300
- const isPayee = userActionRequest.userActionType === "PAYEE_APPROVAL" && Boolean(userActionRequest.metadata?.payeeName);
4301
- const isVerifying = actionType === "approve" && isSubmitting;
4302
- const isCancelling = actionType === "reject" && isSubmitting;
4303
- const modalPosition = keyboardVisible ? "flex-end" : "center";
4304
- return /* @__PURE__ */ jsxRuntime.jsx(
4305
- reactNative.Modal,
4306
- {
4307
- visible: isOpen,
4308
- transparent: true,
4309
- animationType: "fade",
4310
- onRequestClose: () => {
4311
- },
4312
- statusBarTranslucent: reactNative.Platform.OS === "android",
4313
- children: /* @__PURE__ */ jsxRuntime.jsx(
4314
- reactNative.KeyboardAvoidingView,
4315
- {
4316
- style: [s8.keyboardAvoid, { justifyContent: modalPosition }],
4317
- behavior: reactNative.Platform.OS === "ios" ? "padding" : "height",
4318
- keyboardVerticalOffset: reactNative.Platform.OS === "ios" ? 0 : 20,
4319
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s8.modalOverlay, { justifyContent: modalPosition }], children: /* @__PURE__ */ jsxRuntime.jsx(
4320
- reactNative.ScrollView,
4321
- {
4322
- contentContainerStyle: [
4323
- s8.scrollContent,
4324
- { justifyContent: modalPosition }
4325
- ],
4326
- showsVerticalScrollIndicator: false,
4327
- keyboardShouldPersistTaps: "handled",
4328
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s8.dialog, { width: DIALOG_MAX_WIDTH }], children: [
4329
- /* @__PURE__ */ jsxRuntime.jsx(
4330
- reactNative.Pressable,
4331
- {
4332
- onPress: handleReject,
4333
- disabled: isSubmitting,
4334
- style: s8.closeBtn,
4335
- accessibilityLabel: "Close",
4336
- hitSlop: 8,
4337
- children: /* @__PURE__ */ jsxRuntime.jsx(
4338
- lucideReactNative.X,
4339
- {
4340
- size: 16,
4341
- color: PAYMAN.mutedForeground,
4342
- strokeWidth: 2.5
4343
- }
4344
- )
4345
- }
4346
- ),
4347
- /* @__PURE__ */ jsxRuntime.jsx(
4348
- reactNative.Text,
4349
- {
4350
- style: s8.description,
4351
- accessibilityLabel: "payman-modal-desc",
4352
- children: userActionRequest.message
4353
- }
4354
- ),
4355
- isPayment ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.transferLabel, children: "Pay" }) : null,
4356
- isPayment && userActionRequest.metadata?.amount ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.heroAmount, children: formatAmountForDisplay(userActionRequest.metadata.amount) }) : null,
4357
- isPayee ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.transferLabel, children: "Create Payee" }) : null,
4358
- isPayee && userActionRequest.metadata?.payeeName ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.heroPayeeWrap, children: [
4359
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.heroPayeeName, children: userActionRequest.metadata.payeeName }),
4360
- userActionRequest.metadata.payeeType ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.heroPayeeType, children: userActionRequest.metadata.payeeType }) : null
4361
- ] }) : null,
4362
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.otpWrap, accessibilityLabel: "payman-otp-wrap", children: [
4363
- /* @__PURE__ */ jsxRuntime.jsx(
4364
- OtpInput,
4365
- {
4366
- value: otp,
4367
- onChange: setOtp,
4368
- maxLength: schema.maxLength,
4369
- disabled: isSubmitting,
4370
- error: otpError
4371
- }
4372
- ),
4373
- isVerifying ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.verifyingRow, children: [
4374
- /* @__PURE__ */ jsxRuntime.jsx(
4375
- reactNative.ActivityIndicator,
4376
- {
4377
- size: "small",
4378
- color: PAYMAN_BRAND_GREEN
4379
- }
4380
- ),
4381
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.verifyingText, children: MODAL_CONTENT.LOADING_APPROVE })
4382
- ] }) : null
4383
- ] }),
4384
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.linksCol, children: [
4385
- /* @__PURE__ */ jsxRuntime.jsx(
4386
- reactNative.Pressable,
4387
- {
4388
- onPress: handleResend,
4389
- disabled: isSubmitting || resendCooldownRemaining > 0,
4390
- accessibilityLabel: "payman-modal-btn-resend",
4391
- testID: "payman-modal-btn-resend",
4392
- children: /* @__PURE__ */ jsxRuntime.jsx(
4393
- reactNative.Text,
4394
- {
4395
- style: [
4396
- s8.linkText,
4397
- (isSubmitting || resendCooldownRemaining > 0) && s8.linkDisabled
4398
- ],
4399
- children: actionType === "resend" ? MODAL_CONTENT.LOADING_RESEND : resendCooldownRemaining > 0 ? `${MODAL_CONTENT.RESEND_AVAILABLE_IN} ${resendCooldownRemaining}s` : BUTTON_LABELS.RESEND_CODE
4400
- }
4401
- )
4402
- }
4403
- ),
4404
- /* @__PURE__ */ jsxRuntime.jsx(
4405
- reactNative.Pressable,
4406
- {
4407
- onPress: handleReject,
4408
- disabled: isSubmitting,
4409
- accessibilityLabel: "payman-modal-btn-reject",
4410
- testID: "payman-modal-btn-reject",
4411
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6 }, children: [
4412
- isCancelling ? /* @__PURE__ */ jsxRuntime.jsx(
4413
- reactNative.ActivityIndicator,
4414
- {
4415
- size: "small",
4416
- color: PAYMAN.mutedForeground
4417
- }
4418
- ) : null,
4419
- /* @__PURE__ */ jsxRuntime.jsx(
4420
- reactNative.Text,
4421
- {
4422
- style: [
4423
- s8.linkText,
4424
- isSubmitting && !isCancelling && s8.linkDisabled
4425
- ],
4426
- children: isCancelling ? MODAL_CONTENT.LOADING_REJECT : isPayee ? BUTTON_LABELS.CANCEL : BUTTON_LABELS.CANCEL_TRANSFER
4427
- }
4428
- )
4429
- ] })
4430
- }
4431
- )
4432
- ] }),
4433
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.footer, children: [
4434
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.footerPrefix, children: MODAL_CONTENT.SECURED_BY_PREFIX }),
4435
- /* @__PURE__ */ jsxRuntime.jsx(
4436
- reactNative.Image,
4437
- {
4438
- source: { uri: payman_mono_crop_blue_default },
4439
- style: s8.footerLogo,
4440
- resizeMode: "contain",
4441
- accessibilityElementsHidden: true,
4442
- importantForAccessibility: "no-hide-descendants"
4443
- }
4444
- ),
4445
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.footerBrand, children: MODAL_CONTENT.SECURED_BY_BRAND })
4446
- ] })
4447
- ] })
4448
- }
4449
- ) })
4450
- }
4451
- )
4452
- }
4453
- );
4454
- }
4455
- var s8 = reactNative.StyleSheet.create({
4456
- keyboardAvoid: {
4457
- flex: 1,
4458
- width: "100%"
4459
- },
4460
- modalOverlay: {
4461
- flex: 1,
4462
- width: "100%",
4463
- backgroundColor: PAYMAN.modalBackdrop,
4464
- alignItems: "center",
4465
- paddingHorizontal: 16,
4466
- paddingBottom: 24
4467
- },
4468
- scrollContent: {
4469
- flexGrow: 1,
4470
- width: "100%",
4471
- alignItems: "center",
4472
- paddingBottom: 8
4473
- },
4474
- dialog: {
4475
- position: "relative",
4476
- backgroundColor: PAYMAN.card,
4477
- borderRadius: 16,
4478
- paddingTop: 36,
4479
- paddingHorizontal: DIALOG_PADDING,
4480
- paddingBottom: 28,
4481
- borderWidth: 1,
4482
- borderColor: PAYMAN.border
4483
- },
4484
- closeBtn: {
4485
- position: "absolute",
4486
- right: 10,
4487
- top: 10,
4488
- width: 28,
4489
- height: 28,
4490
- alignItems: "center",
4491
- justifyContent: "center",
4492
- zIndex: 2
4493
- },
4494
- description: {
4495
- fontSize: 14,
4496
- lineHeight: 20,
4497
- color: PAYMAN.mutedForeground,
4498
- textAlign: "center",
4499
- marginBottom: 20,
4500
- paddingHorizontal: 24
4501
- },
4502
- transferLabel: {
4503
- fontSize: 12,
4504
- fontWeight: "600",
4505
- letterSpacing: 0.8,
4506
- textTransform: "uppercase",
4507
- color: PAYMAN.mutedForeground,
4508
- textAlign: "center",
4509
- marginBottom: 4,
4510
- opacity: 0.7
4511
- },
4512
- heroAmount: {
4513
- fontSize: 42,
4514
- fontWeight: "700",
4515
- color: PAYMAN_BRAND_GREEN,
4516
- textAlign: "center",
4517
- marginBottom: 32,
4518
- letterSpacing: -0.5
4519
- },
4520
- heroPayeeWrap: {
4521
- alignItems: "center",
4522
- marginBottom: 32,
4523
- gap: 4
4524
- },
4525
- heroPayeeName: {
4526
- fontSize: 28,
4527
- fontWeight: "700",
4528
- color: PAYMAN_BRAND_GREEN,
4529
- textAlign: "center"
4530
- },
4531
- heroPayeeType: {
4532
- fontSize: 10,
4533
- fontWeight: "600",
4534
- letterSpacing: 0.8,
4535
- textTransform: "uppercase",
4536
- color: PAYMAN_BRAND_GREEN,
4537
- opacity: 0.85
4538
- },
4539
- otpWrap: {
4540
- marginBottom: 30
4541
- },
4542
- verifyingRow: {
4543
- flexDirection: "row",
4544
- alignItems: "center",
4545
- justifyContent: "center",
4546
- gap: 6,
4547
- marginTop: 10
4548
- },
4549
- verifyingText: {
4550
- fontSize: 12,
4551
- fontWeight: "500",
4552
- color: PAYMAN_BRAND_GREEN
4553
- },
4554
- linksCol: {
4555
- alignItems: "center",
4556
- gap: 12,
4557
- marginBottom: 32
4558
- },
4559
- linkText: {
4560
- fontSize: 11,
4561
- fontWeight: "600",
4562
- letterSpacing: 0.02,
4563
- color: PAYMAN.mutedForeground,
4564
- textAlign: "center"
4565
- },
4566
- linkDisabled: {
4567
- opacity: 0.4
4568
- },
4569
- footer: {
4570
- flexDirection: "row",
4571
- alignItems: "center",
4572
- justifyContent: "center",
4573
- flexWrap: "nowrap",
4574
- gap: 6,
4575
- borderTopWidth: 1,
4576
- borderTopColor: PAYMAN.border,
4577
- paddingTop: 20
4578
- },
4579
- footerPrefix: {
4580
- fontSize: 11,
4581
- fontWeight: "500",
4582
- color: PAYMAN.mutedForeground,
4583
- opacity: 0.9
4584
- },
4585
- footerLogo: {
4586
- height: 18,
4587
- width: 18
4588
- },
4589
- footerBrand: {
4590
- fontSize: 12,
4591
- fontWeight: "700",
4592
- letterSpacing: 0.3,
4593
- color: "#0A3B44"
4594
- }
4595
- });
4596
-
4597
- // src/utils/formatDate.ts
4598
- function formatDate(timestamp) {
4599
- const date = typeof timestamp === "string" ? new Date(timestamp) : timestamp;
4600
- const now = /* @__PURE__ */ new Date();
4601
- const diffMs = now.getTime() - date.getTime();
4602
- const diffMins = Math.floor(diffMs / 6e4);
4603
- const diffHours = Math.floor(diffMs / 36e5);
4604
- const diffDays = Math.floor(diffMs / 864e5);
4605
- if (diffMins < 1) {
4606
- return "Just now";
4607
- }
4608
- if (diffMins < 60) {
4609
- return `${diffMins}m ago`;
4610
- }
4611
- if (diffHours < 24) {
4612
- return `${diffHours}h ago`;
4613
- }
4614
- if (diffDays < 7) {
4615
- return `${diffDays}d ago`;
4616
- }
4617
- return date.toLocaleDateString("en-US", {
4618
- month: "short",
4619
- day: "numeric",
4620
- year: date.getFullYear() !== now.getFullYear() ? "numeric" : void 0
4621
- });
4622
- }
4623
- function captureSentryError(error, context) {
4624
- if (!Sentry__namespace.getClient()) return;
4625
- const tags = {};
4626
- if (context.executionId) tags.executionId = context.executionId;
4627
- if (context.sessionId) tags.sessionId = context.sessionId;
4628
- if (context.route) tags.route = context.route;
4629
- if (context.cfRay) tags.cfRay = context.cfRay;
4630
- if (context.customerId) tags.customerId = context.customerId;
4631
- if (context.customerEmail) tags.customerEmail = context.customerEmail;
4632
- const contexts = {
4633
- chat_session: {
4634
- sessionId: context.sessionId ?? null,
4635
- sessionOwnerId: context.sessionOwnerId ?? null,
4636
- executionId: context.executionId ?? null,
4637
- workflowName: context.workflowName ?? null,
4638
- cfRay: context.cfRay ?? null,
4639
- customerId: context.customerId ?? null,
4640
- customerEmail: context.customerEmail ?? null
4641
- }
4642
- };
4643
- if (typeof error === "string") {
4644
- Sentry__namespace.captureMessage(error, { level: "error", tags, contexts });
4645
- } else {
4646
- Sentry__namespace.captureException(error, { tags, contexts });
4647
- }
4648
- }
4649
- function FloatingChat({
4650
- config,
4651
- callbacks = {},
4652
- buttonPosition = "bottom-right",
4653
- buttonSize = "md",
4654
- buttonColor,
4655
- buttonIcon,
4656
- buttonIconUrl,
4657
- windowWidth = 380,
4658
- windowHeight = 560,
4659
- headerTitle = "AI Assistant",
4660
- headerSubtitle = "Online \u2022 Ready to help",
4661
- headerAvatar = "\u{1F916}",
4662
- headerColor,
4663
- defaultOpen = false,
4664
- showNotificationBadge = false
4665
- }) {
4666
- const [isOpen, setIsOpen] = React.useState(defaultOpen);
4667
- const toggleChat = () => setIsOpen(!isOpen);
4668
- const buttonSizes = { sm: "w-12 h-12", md: "w-14 h-14", lg: "w-16 h-16" };
4669
- const iconSizes = { sm: "w-5 h-5", md: "w-6 h-6", lg: "w-7 h-7" };
4670
- const positions = {
4671
- "bottom-right": "bottom-5 right-5",
4672
- "bottom-left": "bottom-5 left-5",
4673
- "top-right": "top-5 right-5",
4674
- "top-left": "top-5 left-5"
4675
- };
4676
- const windowPositions = {
4677
- "bottom-right": "bottom-[5.5rem] right-5",
4678
- "bottom-left": "bottom-[5.5rem] left-5",
4679
- "top-right": "top-[5.5rem] right-5",
4680
- "top-left": "top-[5.5rem] left-5"
4681
- };
4682
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4683
- /* @__PURE__ */ jsxRuntime.jsxs(
4684
- "button",
4685
- {
4686
- onClick: toggleChat,
4687
- className: cn(
4688
- "fixed z-[9999] rounded-full shadow-lg",
4689
- "flex items-center justify-center cursor-pointer border-0 outline-none",
4690
- "hover:scale-105 active:scale-95 transition-transform duration-200",
4691
- buttonSizes[buttonSize],
4692
- positions[buttonPosition],
4693
- !buttonColor && "bg-foreground text-background"
4694
- ),
4695
- style: buttonColor ? { background: buttonColor, color: "#fff" } : void 0,
4696
- "aria-label": isOpen ? "Close chat" : "Open chat",
4697
- children: [
4698
- showNotificationBadge && !isOpen && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-0.5 -right-0.5 w-3 h-3 bg-red-500 rounded-full border-2 border-background" }),
4699
- buttonIcon ? buttonIcon : buttonIconUrl ? /* @__PURE__ */ jsxRuntime.jsx(
4700
- "img",
4701
- {
4702
- src: buttonIconUrl,
4703
- alt: "Chat",
4704
- className: cn("object-contain", iconSizes[buttonSize])
4705
- }
4706
- ) : isOpen ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: cn(iconSizes[buttonSize]) }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageCircle, { className: cn(iconSizes[buttonSize]) })
4707
- ]
4708
- }
4709
- ),
4710
- isOpen && /* @__PURE__ */ jsxRuntime.jsxs(
4711
- "div",
4712
- {
4713
- className: cn(
4714
- "fixed z-[9998] bg-card rounded-2xl shadow-2xl overflow-hidden flex flex-col",
4715
- "border border-border/60",
4716
- "animate-fade-in-up",
4717
- windowPositions[buttonPosition]
4718
- ),
4719
- style: {
4720
- width: typeof windowWidth === "number" ? `${windowWidth}px` : windowWidth,
4721
- height: typeof windowHeight === "number" ? `${windowHeight}px` : windowHeight,
4722
- maxWidth: "calc(100vw - 40px)",
4723
- maxHeight: "calc(100vh - 120px)"
4724
- },
4725
- children: [
4726
- /* @__PURE__ */ jsxRuntime.jsxs(
4727
- "div",
4728
- {
4729
- className: cn(
4730
- "px-4 py-3 flex items-center justify-between shrink-0",
4731
- !headerColor && "bg-foreground text-background"
4732
- ),
4733
- style: headerColor ? { background: headerColor, color: "#fff" } : void 0,
4734
- children: [
4735
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
4736
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-9 h-9 rounded-xl bg-white/15 flex items-center justify-center text-lg shrink-0", children: headerAvatar }),
4737
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
4738
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-sm leading-tight truncate", children: headerTitle }),
4739
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] opacity-70 leading-tight truncate", children: headerSubtitle })
4740
- ] })
4741
- ] }),
4742
- /* @__PURE__ */ jsxRuntime.jsx(
4743
- "button",
4744
- {
4745
- onClick: toggleChat,
4746
- className: "w-8 h-8 rounded-lg hover:bg-white/10 flex items-center justify-center transition-colors shrink-0",
4747
- "aria-label": "Minimize chat",
4748
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "w-4 h-4" })
4749
- }
4750
- )
4751
- ]
4752
- }
4753
- ),
4754
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
4755
- PaymanChat,
4756
- {
4757
- config,
4758
- callbacks,
4759
- className: "h-full border-0 rounded-none"
4760
- }
4761
- ) })
4762
- ]
4763
- }
4764
- )
4765
- ] });
4766
- }
4767
- var PaymanChatContext = React.createContext(void 0);
4768
- function usePaymanChat() {
4769
- const ctx = React.useContext(PaymanChatContext);
4770
- if (!ctx) {
4771
- throw new Error("usePaymanChat must be used within a PaymanChat component");
4772
- }
4773
- return ctx;
4774
- }
4775
-
4776
- // src/utils/relativeTime.ts
4777
- function formatRelativeTime(iso, now = /* @__PURE__ */ new Date()) {
4778
- const then = new Date(iso);
4779
- if (Number.isNaN(then.getTime())) return "";
4780
- const diffMs = now.getTime() - then.getTime();
4781
- const diffSec = Math.round(diffMs / 1e3);
4782
- const diffMin = Math.round(diffMs / 6e4);
4783
- const diffHour = Math.round(diffMs / 36e5);
4784
- if (diffSec < 45) return "just now";
4785
- if (diffMin < 60) return `${diffMin}m ago`;
4786
- if (diffHour < 24) return `${diffHour}h ago`;
4787
- const thenDate = new Date(then.getFullYear(), then.getMonth(), then.getDate());
4788
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
4789
- const dayDiff = Math.round(
4790
- (today.getTime() - thenDate.getTime()) / 864e5
4791
- );
4792
- if (dayDiff === 1) return "Yesterday";
4793
- if (dayDiff < 7) {
4794
- return then.toLocaleDateString(void 0, { weekday: "short" });
4795
- }
4796
- const sameYear = then.getFullYear() === now.getFullYear();
4797
- return then.toLocaleDateString(void 0, {
4798
- month: "short",
4799
- day: "numeric",
4800
- year: sameYear ? void 0 : "numeric"
4801
- });
4802
- }
4803
-
4804
- Object.defineProperty(exports, "buildFormattedThinking", {
4805
- enumerable: true,
4806
- get: function () { return paymanTypescriptAskSdk.buildFormattedThinking; }
4807
- });
4808
- Object.defineProperty(exports, "buildScopeKey", {
4809
- enumerable: true,
4810
- get: function () { return paymanTypescriptAskSdk.buildScopeKey; }
4811
- });
4812
- Object.defineProperty(exports, "cancelUserAction", {
4813
- enumerable: true,
4814
- get: function () { return paymanTypescriptAskSdk.cancelUserAction; }
4815
- });
4816
- Object.defineProperty(exports, "createInitialV2State", {
4817
- enumerable: true,
4818
- get: function () { return paymanTypescriptAskSdk.createInitialV2State; }
4819
- });
4820
- Object.defineProperty(exports, "generateId", {
4821
- enumerable: true,
4822
- get: function () { return paymanTypescriptAskSdk.generateId; }
4823
- });
4824
- Object.defineProperty(exports, "listConversations", {
4825
- enumerable: true,
4826
- get: function () { return paymanTypescriptAskSdk.listConversations; }
4827
- });
4828
- Object.defineProperty(exports, "listSessions", {
4829
- enumerable: true,
4830
- get: function () { return paymanTypescriptAskSdk.listSessions; }
4831
- });
4832
- Object.defineProperty(exports, "processStreamEventV2", {
4833
- enumerable: true,
4834
- get: function () { return paymanTypescriptAskSdk.processStreamEventV2; }
4835
- });
4836
- Object.defineProperty(exports, "resendUserAction", {
4837
- enumerable: true,
4838
- get: function () { return paymanTypescriptAskSdk.resendUserAction; }
4839
- });
4840
- Object.defineProperty(exports, "streamWorkflowEvents", {
4841
- enumerable: true,
4842
- get: function () { return paymanTypescriptAskSdk.streamWorkflowEvents; }
4843
- });
4844
- Object.defineProperty(exports, "submitUserAction", {
4845
- enumerable: true,
4846
- get: function () { return paymanTypescriptAskSdk.submitUserAction; }
4847
- });
4848
- Object.defineProperty(exports, "useChatV2", {
4849
- enumerable: true,
4850
- get: function () { return paymanTypescriptAskSdk.useChatV2; }
4851
- });
4852
- Object.defineProperty(exports, "useVoice", {
4853
- enumerable: true,
4854
- get: function () { return paymanTypescriptAskSdk.useVoice; }
4855
- });
4856
- exports.ChatHeader = ChatHeader;
4857
- exports.FloatingChat = FloatingChat;
4858
- exports.PaymanChat = PaymanChat;
4859
- exports.PaymanChatContext = PaymanChatContext;
4860
- exports.SessionHistorySidebar = SessionHistorySidebar;
4861
- exports.UserActionModal = UserActionModal;
4862
- exports.captureSentryError = captureSentryError;
4863
- exports.cn = cn;
4864
- exports.formatDate = formatDate;
4865
- exports.formatRelativeTime = formatRelativeTime;
4866
- exports.usePaymanChat = usePaymanChat;
4867
- exports.useSessionHistory = useSessionHistory;
4868
- //# sourceMappingURL=index.native.js.map
4869
- //# sourceMappingURL=index.native.js.map