@paymanai/payman-ask-sdk 2.0.3 → 2.0.5

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,17 +1,20 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React = require('react');
4
4
  var reactNative = require('react-native');
5
+ var paymanTypescriptAskSdk = require('@paymanai/payman-typescript-ask-sdk');
6
+ var lucideReactNative = require('lucide-react-native');
5
7
  var jsxRuntime = require('react/jsx-runtime');
8
+ var Markdown = require('react-native-markdown-display');
6
9
  var lucideReact = require('lucide-react');
7
10
  var framerMotion = require('framer-motion');
8
11
  var reactDom = require('react-dom');
9
12
  var clsx = require('clsx');
10
13
  var tailwindMerge = require('tailwind-merge');
11
- var paymanTypescriptAskSdk = require('@paymanai/payman-typescript-ask-sdk');
12
- var lucideReactNative = require('lucide-react-native');
13
14
  var Sentry = require('@sentry/react');
14
15
 
16
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
+
15
18
  function _interopNamespace(e) {
16
19
  if (e && e.__esModule) return e;
17
20
  var n = Object.create(null);
@@ -30,39 +33,2916 @@ function _interopNamespace(e) {
30
33
  return Object.freeze(n);
31
34
  }
32
35
 
36
+ var React__default = /*#__PURE__*/_interopDefault(React);
37
+ var Markdown__default = /*#__PURE__*/_interopDefault(Markdown);
33
38
  var Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
34
39
 
35
40
  // src/components/PaymanChat/index.native.tsx
36
- var PaymanChat = react.forwardRef(function PaymanChat2(_props, _ref) {
37
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.root, children: [
38
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.title, children: "PaymanChat v2" }),
39
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles.body, children: "React Native support is coming in a follow-up release. Please use the web SDK for now." })
40
- ] });
41
- });
42
- var styles = reactNative.StyleSheet.create({
43
- root: {
44
- flex: 1,
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,
45
471
  alignItems: "center",
46
472
  justifyContent: "center",
47
- padding: 24
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
48
536
  },
49
537
  title: {
50
538
  fontSize: 16,
51
539
  fontWeight: "600",
52
- marginBottom: 8
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 WaveformBar = React.memo(
2239
+ ({
2240
+ delay,
2241
+ isActive,
2242
+ color
2243
+ }) => {
2244
+ const height = React.useRef(new reactNative.Animated.Value(6)).current;
2245
+ React.useEffect(() => {
2246
+ if (isActive) {
2247
+ const animation = reactNative.Animated.loop(
2248
+ reactNative.Animated.sequence([
2249
+ reactNative.Animated.timing(height, {
2250
+ toValue: 14 + Math.random() * 10,
2251
+ duration: 300 + Math.random() * 50,
2252
+ delay,
2253
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
2254
+ useNativeDriver: false
2255
+ }),
2256
+ reactNative.Animated.timing(height, {
2257
+ toValue: 6 + Math.random() * 4,
2258
+ duration: 300 + Math.random() * 50,
2259
+ easing: reactNative.Easing.inOut(reactNative.Easing.ease),
2260
+ useNativeDriver: false
2261
+ })
2262
+ ])
2263
+ );
2264
+ animation.start();
2265
+ return () => animation.stop();
2266
+ } else {
2267
+ reactNative.Animated.timing(height, {
2268
+ toValue: 6,
2269
+ duration: 300,
2270
+ useNativeDriver: false
2271
+ }).start();
2272
+ }
2273
+ }, [isActive, delay, height]);
2274
+ return /* @__PURE__ */ jsxRuntime.jsx(
2275
+ reactNative.Animated.View,
2276
+ {
2277
+ style: [styles.waveformBar, { height, backgroundColor: color }]
2278
+ }
2279
+ );
2280
+ }
2281
+ );
2282
+ WaveformBar.displayName = "WaveformBar";
2283
+ var RollingText = React.memo(({ text, color }) => {
2284
+ const [lines, setLines] = React__default.default.useState([]);
2285
+ const [currentLineIndex, setCurrentLineIndex] = React__default.default.useState(0);
2286
+ const translateY = React.useRef(new reactNative.Animated.Value(0)).current;
2287
+ const opacity = React.useRef(new reactNative.Animated.Value(1)).current;
2288
+ React.useEffect(() => {
2289
+ if (!text) {
2290
+ setLines([]);
2291
+ setCurrentLineIndex(0);
2292
+ return;
2293
+ }
2294
+ const words = text.split(" ");
2295
+ const newLines = [];
2296
+ let currentLine = "";
2297
+ words.forEach((word) => {
2298
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
2299
+ if (testLine.length > 35 && currentLine) {
2300
+ newLines.push(currentLine);
2301
+ currentLine = word;
2302
+ } else {
2303
+ currentLine = testLine;
2304
+ }
2305
+ });
2306
+ if (currentLine) {
2307
+ newLines.push(currentLine);
2308
+ }
2309
+ setLines(newLines);
2310
+ if (newLines.length > 0) {
2311
+ const newIndex = newLines.length - 1;
2312
+ if (newIndex > currentLineIndex) {
2313
+ reactNative.Animated.sequence([
2314
+ reactNative.Animated.parallel([
2315
+ reactNative.Animated.timing(translateY, {
2316
+ toValue: -20,
2317
+ duration: 150,
2318
+ useNativeDriver: true
2319
+ }),
2320
+ reactNative.Animated.timing(opacity, {
2321
+ toValue: 0,
2322
+ duration: 150,
2323
+ useNativeDriver: true
2324
+ })
2325
+ ]),
2326
+ reactNative.Animated.timing(translateY, {
2327
+ toValue: 20,
2328
+ duration: 0,
2329
+ useNativeDriver: true
2330
+ }),
2331
+ reactNative.Animated.parallel([
2332
+ reactNative.Animated.timing(translateY, {
2333
+ toValue: 0,
2334
+ duration: 200,
2335
+ easing: reactNative.Easing.out(reactNative.Easing.ease),
2336
+ useNativeDriver: true
2337
+ }),
2338
+ reactNative.Animated.timing(opacity, {
2339
+ toValue: 1,
2340
+ duration: 200,
2341
+ useNativeDriver: true
2342
+ })
2343
+ ])
2344
+ ]).start();
2345
+ setCurrentLineIndex(newIndex);
2346
+ }
2347
+ }
2348
+ }, [text, currentLineIndex, translateY, opacity]);
2349
+ const currentText = lines[currentLineIndex] || text;
2350
+ return /* @__PURE__ */ jsxRuntime.jsx(
2351
+ reactNative.Animated.Text,
2352
+ {
2353
+ style: [
2354
+ styles.transcribedText,
2355
+ {
2356
+ color,
2357
+ opacity,
2358
+ transform: [{ translateY }]
2359
+ }
2360
+ ],
2361
+ numberOfLines: 1,
2362
+ children: currentText
2363
+ }
2364
+ );
2365
+ });
2366
+ RollingText.displayName = "RollingText";
2367
+ var VoiceOverlay = React.memo(
2368
+ ({
2369
+ visible,
2370
+ voiceState,
2371
+ transcribedText,
2372
+ onStopRecording
2373
+ }) => {
2374
+ const panelHeight = React.useRef(new reactNative.Animated.Value(0)).current;
2375
+ const contentOpacity = React.useRef(new reactNative.Animated.Value(0)).current;
2376
+ const [mounted, setMounted] = React.useState(false);
2377
+ const isListening = voiceState === "listening";
2378
+ const PANEL_CONTENT_HEIGHT = 160;
2379
+ const bgColor = "#15687d";
2380
+ const textColor = "#ffffff";
2381
+ const mutedColor = "rgba(255,255,255,0.75)";
2382
+ const primaryColor = "#ffffff";
2383
+ React.useEffect(() => {
2384
+ if (visible) {
2385
+ setMounted(true);
2386
+ reactNative.Animated.sequence([
2387
+ reactNative.Animated.timing(panelHeight, {
2388
+ toValue: PANEL_CONTENT_HEIGHT,
2389
+ duration: 300,
2390
+ easing: reactNative.Easing.out(reactNative.Easing.cubic),
2391
+ useNativeDriver: false
2392
+ }),
2393
+ reactNative.Animated.timing(contentOpacity, {
2394
+ toValue: 1,
2395
+ duration: 150,
2396
+ useNativeDriver: true
2397
+ })
2398
+ ]).start();
2399
+ } else {
2400
+ reactNative.Animated.parallel([
2401
+ reactNative.Animated.timing(contentOpacity, {
2402
+ toValue: 0,
2403
+ duration: 100,
2404
+ useNativeDriver: true
2405
+ }),
2406
+ reactNative.Animated.timing(panelHeight, {
2407
+ toValue: 0,
2408
+ duration: 220,
2409
+ easing: reactNative.Easing.in(reactNative.Easing.ease),
2410
+ useNativeDriver: false
2411
+ })
2412
+ ]).start(() => setMounted(false));
2413
+ }
2414
+ }, [visible, panelHeight, contentOpacity]);
2415
+ if (!mounted) return null;
2416
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onStopRecording, style: styles.panelWrapper, children: /* @__PURE__ */ jsxRuntime.jsx(
2417
+ reactNative.Animated.View,
2418
+ {
2419
+ style: [
2420
+ styles.panel,
2421
+ {
2422
+ height: panelHeight,
2423
+ backgroundColor: bgColor
2424
+ }
2425
+ ],
2426
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles.content, { opacity: contentOpacity }], children: [
2427
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.textContainer, children: transcribedText.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(RollingText, { text: transcribedText, color: textColor }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.placeholderText, { color: mutedColor }], children: "Listening..." }) }),
2428
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles.controlsContainer, children: [
2429
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.waveformContainer, children: [0, 1, 2, 3, 4].map((index) => /* @__PURE__ */ jsxRuntime.jsx(
2430
+ WaveformBar,
2431
+ {
2432
+ delay: index * 20,
2433
+ isActive: isListening,
2434
+ color: primaryColor
2435
+ },
2436
+ index
2437
+ )) }),
2438
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.hintText, { color: mutedColor }], children: "Tap to stop" })
2439
+ ] })
2440
+ ] })
2441
+ }
2442
+ ) });
2443
+ }
2444
+ );
2445
+ VoiceOverlay.displayName = "VoiceOverlay";
2446
+ var styles = reactNative.StyleSheet.create({
2447
+ panelWrapper: {
2448
+ zIndex: 10,
2449
+ elevation: 10
2450
+ },
2451
+ panel: {
2452
+ overflow: "hidden",
2453
+ borderTopLeftRadius: 20,
2454
+ borderTopRightRadius: 20
2455
+ },
2456
+ content: {
2457
+ flex: 1,
2458
+ paddingHorizontal: 24,
2459
+ paddingTop: 20
2460
+ },
2461
+ textContainer: {
2462
+ height: 32,
2463
+ justifyContent: "center",
2464
+ marginBottom: 16,
2465
+ overflow: "hidden"
2466
+ },
2467
+ transcribedText: {
2468
+ fontSize: 18,
2469
+ fontWeight: "500",
2470
+ textAlign: "left"
2471
+ },
2472
+ placeholderText: {
2473
+ fontSize: 15,
2474
+ textAlign: "left",
2475
+ fontWeight: "800"
2476
+ },
2477
+ controlsContainer: {
2478
+ flex: 1,
2479
+ alignItems: "center",
2480
+ justifyContent: "flex-start"
2481
+ },
2482
+ waveformContainer: {
2483
+ flexDirection: "row",
2484
+ alignItems: "center",
2485
+ justifyContent: "center",
2486
+ gap: 5,
2487
+ height: 28,
2488
+ marginBottom: 12
2489
+ },
2490
+ waveformBar: {
2491
+ width: 3,
2492
+ borderRadius: 2
2493
+ },
2494
+ hintText: {
2495
+ fontSize: 12
2496
+ }
2497
+ });
2498
+ var DEFAULT_ACCENT = "#00858d";
2499
+ function getPalette(theme, accent) {
2500
+ if (theme === "dark") {
2501
+ return {
2502
+ background: "#0d1719",
2503
+ textPrimary: "#f5f8f8",
2504
+ textSecondary: "rgba(245,248,248,0.72)",
2505
+ textMuted: "rgba(245,248,248,0.45)",
2506
+ heroHaloFrom: "rgba(0,133,141,0.16)",
2507
+ heroHaloTo: "rgba(0,133,141,0.06)",
2508
+ heroRing: "rgba(0,133,141,0.35)",
2509
+ heroDot: accent,
2510
+ disabledText: "rgba(245,248,248,0.45)"
2511
+ };
2512
+ }
2513
+ return {
2514
+ background: "#ffffff",
2515
+ textPrimary: "#1d1d1f",
2516
+ textSecondary: "#5e5d5a",
2517
+ textMuted: "#8a8a86",
2518
+ heroHaloFrom: "rgba(0,133,141,0.10)",
2519
+ heroHaloTo: "rgba(0,133,141,0.02)",
2520
+ heroRing: "rgba(0,133,141,0.20)",
2521
+ heroDot: accent,
2522
+ disabledText: "#8a8a86"
2523
+ };
2524
+ }
2525
+ var PaymanChat = React.forwardRef(
2526
+ function PaymanChat2({ config, callbacks = {}, children }, ref) {
2527
+ const {
2528
+ messages,
2529
+ sendMessage,
2530
+ clearMessages,
2531
+ cancelStream,
2532
+ resetSession: resetChatSession,
2533
+ getSessionId,
2534
+ getMessages,
2535
+ isWaitingForResponse,
2536
+ userActionState,
2537
+ approveUserAction,
2538
+ rejectUserAction,
2539
+ resendOtp,
2540
+ loadSession,
2541
+ loadingSessionId
2542
+ } = paymanTypescriptAskSdk.useChatV2(config, callbacks);
2543
+ const voiceEnabled = config.ui?.input?.voice === true || typeof config.ui?.input?.voice === "object" && config.ui.input.voice !== null;
2544
+ const {
2545
+ voiceState,
2546
+ transcribedText,
2547
+ isAvailable: voiceAvailable,
2548
+ isRecording,
2549
+ startRecording,
2550
+ stopRecording,
2551
+ clearTranscript
2552
+ } = paymanTypescriptAskSdk.useVoice();
2553
+ const attachmentsCfg = config.ui?.input?.attachments;
2554
+ const attachmentsAllowed = attachmentsCfg === void 0 || attachmentsCfg === true || typeof attachmentsCfg === "object" && attachmentsCfg !== null;
2555
+ const showUploadImage = attachmentsAllowed && (typeof attachmentsCfg !== "object" || attachmentsCfg === null ? true : attachmentsCfg.uploadImage !== false);
2556
+ const showAttachFile = attachmentsAllowed && (typeof attachmentsCfg !== "object" || attachmentsCfg === null ? true : attachmentsCfg.attachFile !== false);
2557
+ const [hasEverSentMessage, setHasEverSentMessage] = React.useState(false);
2558
+ const chatInputRef = React.useRef(null);
2559
+ const messageListRef = React.useRef(null);
2560
+ const resetToEmptyRef = React.useRef(false);
2561
+ React.useEffect(() => {
2562
+ if (resetToEmptyRef.current) {
2563
+ if (messages.length === 0) {
2564
+ setHasEverSentMessage(false);
2565
+ resetToEmptyRef.current = false;
2566
+ }
2567
+ return;
2568
+ }
2569
+ if (messages.length > 0 && !hasEverSentMessage) {
2570
+ setHasEverSentMessage(true);
2571
+ }
2572
+ }, [messages.length, hasEverSentMessage]);
2573
+ const prevRecordingRef = React.useRef(isRecording);
2574
+ React.useEffect(() => {
2575
+ const wasRecording = prevRecordingRef.current;
2576
+ prevRecordingRef.current = isRecording;
2577
+ if (wasRecording && !isRecording && transcribedText.trim()) {
2578
+ chatInputRef.current?.setDraft(transcribedText.trim());
2579
+ }
2580
+ }, [isRecording, transcribedText]);
2581
+ const handleVoicePress = React.useCallback(async () => {
2582
+ if (!voiceAvailable) return;
2583
+ if (isRecording) {
2584
+ stopRecording();
2585
+ return;
2586
+ }
2587
+ clearTranscript();
2588
+ await startRecording();
2589
+ }, [voiceAvailable, isRecording, stopRecording, clearTranscript, startRecording]);
2590
+ const handleSend = React.useCallback(
2591
+ (text) => {
2592
+ if (isRecording) stopRecording();
2593
+ if (!text.trim()) return;
2594
+ void sendMessage(text.trim());
2595
+ },
2596
+ [isRecording, stopRecording, sendMessage]
2597
+ );
2598
+ const dismissKeyboard = React.useCallback(() => {
2599
+ reactNative.Keyboard.dismiss();
2600
+ }, []);
2601
+ const handleEditMessageDraft = React.useCallback(
2602
+ (messageId) => {
2603
+ const targetMessage = messages.find(
2604
+ (message) => message.id === messageId && message.role === "user"
2605
+ );
2606
+ if (!targetMessage?.content.trim()) return;
2607
+ chatInputRef.current?.setDraft(targetMessage.content);
2608
+ requestAnimationFrame(() => {
2609
+ messageListRef.current?.scrollToBottom(true);
2610
+ });
2611
+ },
2612
+ [messages]
2613
+ );
2614
+ const handleRetryUserMessage = React.useCallback(
2615
+ (messageId) => {
2616
+ if (isWaitingForResponse) return;
2617
+ const targetMessage = messages.find(
2618
+ (message) => message.id === messageId && message.role === "user"
2619
+ );
2620
+ if (!targetMessage?.content.trim()) return;
2621
+ void sendMessage(targetMessage.content.trim());
2622
+ requestAnimationFrame(() => {
2623
+ messageListRef.current?.scrollToBottom(false);
2624
+ });
2625
+ },
2626
+ [isWaitingForResponse, messages, sendMessage]
2627
+ );
2628
+ const performResetSession = React.useCallback(() => {
2629
+ resetToEmptyRef.current = true;
2630
+ if (isRecording) stopRecording();
2631
+ clearTranscript();
2632
+ chatInputRef.current?.setDraft("");
2633
+ resetChatSession();
2634
+ callbacks.onResetSession?.();
2635
+ }, [isRecording, stopRecording, clearTranscript, resetChatSession, callbacks]);
2636
+ React.useImperativeHandle(
2637
+ ref,
2638
+ () => ({
2639
+ resetSession: performResetSession,
2640
+ clearMessages,
2641
+ cancelStream,
2642
+ getSessionId,
2643
+ getMessages,
2644
+ loadSession
2645
+ }),
2646
+ [performResetSession, clearMessages, cancelStream, getSessionId, getMessages, loadSession]
2647
+ );
2648
+ const ui = config.ui ?? {};
2649
+ const theme = ui.theme === "dark" ? "dark" : "light";
2650
+ const accent = ui.accent ?? DEFAULT_ACCENT;
2651
+ const palette = React.useMemo(() => getPalette(theme, accent), [theme, accent]);
2652
+ const placeholder = ui.input?.placeholder ?? "Type your message\u2026";
2653
+ const showResetButton = ui.input?.showResetButton ?? false;
2654
+ const messageActionsConfig = ui.messages?.actions;
2655
+ const messageActions = React.useMemo(
2656
+ () => ({
2657
+ userMessageActions: {
2658
+ copy: messageActionsConfig?.userMessageActions?.copy ?? true,
2659
+ edit: messageActionsConfig?.userMessageActions?.edit ?? false,
2660
+ retry: messageActionsConfig?.userMessageActions?.retry ?? false
2661
+ },
2662
+ assistantMessageActions: {
2663
+ copy: messageActionsConfig?.assistantMessageActions?.copy ?? true,
2664
+ trace: messageActionsConfig?.assistantMessageActions?.trace ?? true
2665
+ }
2666
+ }),
2667
+ [messageActionsConfig]
2668
+ );
2669
+ const isEmpty = messages.length === 0;
2670
+ const showEmptyShell = isEmpty && !hasEverSentMessage;
2671
+ const emptyStateText = ui.emptyState?.text ?? "How may I assist you today?";
2672
+ const emptyStateEyebrow = ui.emptyState?.eyebrow;
2673
+ const showEmptyIcon = ui.emptyState?.icon ?? true;
2674
+ const emptyStateLogo = ui.emptyState?.logo;
2675
+ const availability = config.availability;
2676
+ if (availability?.state === "disabled") {
2677
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s7.root, { backgroundColor: palette.background }], children: [
2678
+ children,
2679
+ /* @__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" }) })
2680
+ ] });
2681
+ }
2682
+ const isReadOnly = availability?.state === "readOnly";
2683
+ const hideSendForVerification = userActionState.request != null && userActionState.result == null;
2684
+ const verificationPanel = userActionState.request ? /* @__PURE__ */ jsxRuntime.jsx(
2685
+ InlineVerificationPanel,
2686
+ {
2687
+ userActionRequest: userActionState.request,
2688
+ clearOtpTrigger: userActionState.clearOtpTrigger,
2689
+ onApprove: approveUserAction,
2690
+ onReject: rejectUserAction,
2691
+ onResend: resendOtp
2692
+ }
2693
+ ) : null;
2694
+ const inputProps = {
2695
+ onSend: handleSend,
2696
+ onCancel: cancelStream,
2697
+ disabled: false,
2698
+ isStreaming: isWaitingForResponse,
2699
+ placeholder: isRecording ? "Listening\u2026" : placeholder,
2700
+ enableVoice: voiceEnabled,
2701
+ voiceAvailable: voiceEnabled && voiceAvailable,
2702
+ isRecording,
2703
+ onVoicePress: voiceEnabled ? handleVoicePress : void 0,
2704
+ showResetSession: showResetButton,
2705
+ onResetSession: performResetSession,
2706
+ showUploadImageButton: showUploadImage,
2707
+ showAttachFileButton: showAttachFile,
2708
+ onUploadImageClick: callbacks.onUploadImageClick,
2709
+ onAttachFileClick: callbacks.onAttachFileClick,
2710
+ theme,
2711
+ hideSendButton: hideSendForVerification,
2712
+ topAccessory: verificationPanel
2713
+ };
2714
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2715
+ reactNative.KeyboardAvoidingView,
2716
+ {
2717
+ style: [s7.root, { backgroundColor: palette.background }],
2718
+ behavior: reactNative.Platform.OS === "ios" ? "padding" : "height",
2719
+ keyboardVerticalOffset: 0,
2720
+ children: [
2721
+ children,
2722
+ showEmptyShell ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s7.emptyStateRoot, children: [
2723
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s7.emptyStateCenter, onTouchStart: dismissKeyboard, children: [
2724
+ emptyStateLogo ? emptyStateLogo : showEmptyIcon ? /* @__PURE__ */ jsxRuntime.jsx(HeroMark, { palette }) : null,
2725
+ emptyStateEyebrow ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.emptyEyebrow, { color: palette.textMuted }], children: emptyStateEyebrow }) : null,
2726
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.emptyStateText, { color: "#94a3b8" }], children: emptyStateText }),
2727
+ !emptyStateLogo ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [s7.emptyStateSubtext, { color: palette.textSecondary }], children: "Ask anything about your accounts, payments, or transfers." }) : null
2728
+ ] }),
2729
+ !isReadOnly ? /* @__PURE__ */ jsxRuntime.jsx(ChatInputV2, { ref: chatInputRef, ...inputProps }) : null
2730
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2731
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: s7.messageListWrapper, onTouchStart: dismissKeyboard, children: /* @__PURE__ */ jsxRuntime.jsx(
2732
+ MessageListV2,
2733
+ {
2734
+ ref: messageListRef,
2735
+ messages,
2736
+ isStreaming: isWaitingForResponse,
2737
+ isLoadingSession: !!loadingSessionId,
2738
+ onEditUserMessage: handleEditMessageDraft,
2739
+ onRetryUserMessage: handleRetryUserMessage,
2740
+ onExecutionTraceClick: callbacks.onExecutionTraceClick,
2741
+ messageActions,
2742
+ retryDisabled: isWaitingForResponse
2743
+ }
2744
+ ) }),
2745
+ voiceEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
2746
+ VoiceOverlay,
2747
+ {
2748
+ visible: isRecording,
2749
+ voiceState,
2750
+ transcribedText,
2751
+ onStopRecording: stopRecording
2752
+ }
2753
+ ) : null,
2754
+ !isReadOnly ? /* @__PURE__ */ jsxRuntime.jsx(
2755
+ ChatInputV2,
2756
+ {
2757
+ ref: chatInputRef,
2758
+ ...inputProps
2759
+ }
2760
+ ) : null
2761
+ ] })
2762
+ ]
2763
+ }
2764
+ );
2765
+ }
2766
+ );
2767
+ function HeroMark({ palette }) {
2768
+ const scale = React.useRef(new reactNative.Animated.Value(0.92)).current;
2769
+ const opacity = React.useRef(new reactNative.Animated.Value(0)).current;
2770
+ const ringSpin = React.useRef(new reactNative.Animated.Value(0)).current;
2771
+ const dotPulse = React.useRef(new reactNative.Animated.Value(0)).current;
2772
+ React.useEffect(() => {
2773
+ reactNative.Animated.parallel([
2774
+ reactNative.Animated.timing(opacity, {
2775
+ toValue: 1,
2776
+ duration: 420,
2777
+ easing: reactNative.Easing.out(reactNative.Easing.cubic),
2778
+ useNativeDriver: true
2779
+ }),
2780
+ reactNative.Animated.spring(scale, {
2781
+ toValue: 1,
2782
+ friction: 6,
2783
+ tension: 90,
2784
+ useNativeDriver: true
2785
+ })
2786
+ ]).start();
2787
+ reactNative.Animated.loop(
2788
+ reactNative.Animated.timing(ringSpin, {
2789
+ toValue: 1,
2790
+ duration: 14e3,
2791
+ easing: reactNative.Easing.linear,
2792
+ useNativeDriver: true
2793
+ })
2794
+ ).start();
2795
+ reactNative.Animated.loop(
2796
+ reactNative.Animated.sequence([
2797
+ reactNative.Animated.timing(dotPulse, {
2798
+ toValue: 1,
2799
+ duration: 1600,
2800
+ easing: reactNative.Easing.inOut(reactNative.Easing.cubic),
2801
+ useNativeDriver: true
2802
+ }),
2803
+ reactNative.Animated.timing(dotPulse, {
2804
+ toValue: 0,
2805
+ duration: 1600,
2806
+ easing: reactNative.Easing.inOut(reactNative.Easing.cubic),
2807
+ useNativeDriver: true
2808
+ })
2809
+ ])
2810
+ ).start();
2811
+ }, [opacity, scale, ringSpin, dotPulse]);
2812
+ const ringRotate = ringSpin.interpolate({
2813
+ inputRange: [0, 1],
2814
+ outputRange: ["0deg", "360deg"]
2815
+ });
2816
+ const dotScale = dotPulse.interpolate({
2817
+ inputRange: [0, 1],
2818
+ outputRange: [0.92, 1.06]
2819
+ });
2820
+ return /* @__PURE__ */ jsxRuntime.jsx(
2821
+ reactNative.Animated.View,
2822
+ {
2823
+ style: [hero.halo, {
2824
+ backgroundColor: palette.heroHaloFrom,
2825
+ opacity,
2826
+ transform: [{ scale }]
2827
+ }],
2828
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [hero.haloInner, { backgroundColor: palette.heroHaloTo }], children: [
2829
+ /* @__PURE__ */ jsxRuntime.jsx(
2830
+ reactNative.Animated.View,
2831
+ {
2832
+ style: [
2833
+ hero.ring,
2834
+ { borderColor: palette.heroRing, transform: [{ rotate: ringRotate }] }
2835
+ ]
2836
+ }
2837
+ ),
2838
+ /* @__PURE__ */ jsxRuntime.jsx(
2839
+ reactNative.Animated.View,
2840
+ {
2841
+ style: [
2842
+ hero.dot,
2843
+ { backgroundColor: palette.heroDot, transform: [{ scale: dotScale }] }
2844
+ ]
2845
+ }
2846
+ )
2847
+ ] })
2848
+ }
2849
+ );
2850
+ }
2851
+ var s7 = reactNative.StyleSheet.create({
2852
+ root: {
2853
+ flex: 1,
2854
+ overflow: "hidden"
2855
+ },
2856
+ messageListWrapper: {
2857
+ flex: 1,
2858
+ minHeight: 0
2859
+ },
2860
+ emptyStateRoot: {
2861
+ flex: 1,
2862
+ justifyContent: "flex-end"
2863
+ },
2864
+ emptyStateCenter: {
2865
+ flex: 1,
2866
+ alignItems: "center",
2867
+ justifyContent: "center",
2868
+ paddingHorizontal: 32,
2869
+ paddingBottom: 24
2870
+ },
2871
+ emptyEyebrow: {
2872
+ marginTop: 28,
2873
+ marginBottom: 8,
2874
+ fontSize: 13,
2875
+ fontWeight: "600",
2876
+ letterSpacing: 0.4,
2877
+ textTransform: "uppercase"
2878
+ },
2879
+ emptyStateText: {
2880
+ fontSize: 30,
2881
+ fontWeight: "600",
2882
+ textAlign: "center",
2883
+ letterSpacing: -0.7,
2884
+ lineHeight: 36,
2885
+ // Lean into a serif on iOS for a Claude-like editorial feel; system on Android
2886
+ ...reactNative.Platform.select({
2887
+ ios: { fontFamily: "Georgia" },
2888
+ default: {}
2889
+ }),
2890
+ marginTop: 24
2891
+ },
2892
+ emptyStateSubtext: {
2893
+ marginTop: 10,
2894
+ fontSize: 15,
2895
+ lineHeight: 22,
2896
+ textAlign: "center",
2897
+ letterSpacing: -0.15,
2898
+ maxWidth: 320
2899
+ },
2900
+ disabledContainer: {
2901
+ flex: 1,
2902
+ alignItems: "center",
2903
+ justifyContent: "center",
2904
+ padding: 16
2905
+ },
2906
+ disabledText: {
2907
+ fontSize: 14,
2908
+ textAlign: "center"
2909
+ }
2910
+ });
2911
+ var hero = reactNative.StyleSheet.create({
2912
+ halo: {
2913
+ width: 96,
2914
+ height: 96,
2915
+ borderRadius: 48,
2916
+ alignItems: "center",
2917
+ justifyContent: "center"
2918
+ },
2919
+ haloInner: {
2920
+ width: 76,
2921
+ height: 76,
2922
+ borderRadius: 38,
2923
+ alignItems: "center",
2924
+ justifyContent: "center"
53
2925
  },
54
- body: {
55
- fontSize: 13,
56
- color: "#666",
57
- textAlign: "center"
2926
+ ring: {
2927
+ position: "absolute",
2928
+ width: 60,
2929
+ height: 60,
2930
+ borderRadius: 30,
2931
+ borderWidth: 1.5,
2932
+ borderStyle: "dashed"
2933
+ },
2934
+ dot: {
2935
+ width: 18,
2936
+ height: 18,
2937
+ borderRadius: 9
58
2938
  }
59
2939
  });
60
2940
  function cn(...inputs) {
61
2941
  return tailwindMerge.twMerge(clsx.clsx(inputs));
62
2942
  }
63
- var DEFAULT_WIDTH = 280;
64
- var DEFAULT_MIN_WIDTH = 240;
65
- var DEFAULT_MAX_WIDTH = 480;
2943
+ var DEFAULT_WIDTH = 260;
2944
+ var DEFAULT_MIN_WIDTH = 220;
2945
+ var DEFAULT_MAX_WIDTH = 440;
66
2946
  var DEFAULT_PAGE_SIZE = 20;
67
2947
  function getPersistKey(config, opts) {
68
2948
  if (opts.persistKey) return opts.persistKey;
@@ -94,24 +2974,24 @@ function useSessionHistory(config, options = {}, optimisticActivity) {
94
2974
  const minWidth = options.minWidth ?? DEFAULT_MIN_WIDTH;
95
2975
  const maxWidth = options.maxWidth ?? DEFAULT_MAX_WIDTH;
96
2976
  const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
97
- const persistKey = react.useMemo(
2977
+ const persistKey = React.useMemo(
98
2978
  () => getPersistKey(config, options),
99
2979
  [config.workflow.id, options.persistKey]
100
2980
  );
101
- const [width, setWidthState] = react.useState(() => {
2981
+ const [width, setWidthState] = React.useState(() => {
102
2982
  const persisted = loadPersistedState(persistKey);
103
2983
  return persisted?.width ?? defaultWidth;
104
2984
  });
105
- const [collapsed, setCollapsedState] = react.useState(() => {
2985
+ const [collapsed, setCollapsedState] = React.useState(() => {
106
2986
  const persisted = loadPersistedState(persistKey);
107
2987
  return persisted?.collapsed ?? options.defaultCollapsed ?? false;
108
2988
  });
109
- react.useEffect(() => {
2989
+ React.useEffect(() => {
110
2990
  const persisted = loadPersistedState(persistKey);
111
2991
  setWidthState(persisted?.width ?? defaultWidth);
112
2992
  setCollapsedState(persisted?.collapsed ?? options.defaultCollapsed ?? false);
113
2993
  }, [persistKey]);
114
- const setWidth = react.useCallback(
2994
+ const setWidth = React.useCallback(
115
2995
  (px) => {
116
2996
  const clamped = Math.max(minWidth, Math.min(maxWidth, Math.round(px)));
117
2997
  setWidthState(clamped);
@@ -119,7 +2999,7 @@ function useSessionHistory(config, options = {}, optimisticActivity) {
119
2999
  },
120
3000
  [collapsed, maxWidth, minWidth, persistKey]
121
3001
  );
122
- const setCollapsed = react.useCallback(
3002
+ const setCollapsed = React.useCallback(
123
3003
  (value) => {
124
3004
  setCollapsedState((prev) => {
125
3005
  const next = typeof value === "function" ? value(prev) : value;
@@ -130,15 +3010,15 @@ function useSessionHistory(config, options = {}, optimisticActivity) {
130
3010
  [persistKey, width]
131
3011
  );
132
3012
  const isReady = !!(config.session?.owner?.id && (config.workflow.id || config.workflow.name));
133
- const [sessions, setSessions] = react.useState([]);
134
- const [isLoading, setIsLoading] = react.useState(false);
135
- const [error, setError] = react.useState(null);
136
- const [page, setPage] = react.useState(0);
137
- const [hasNext, setHasNext] = react.useState(false);
138
- const inFlightRef = react.useRef(null);
139
- const configRef = react.useRef(config);
3013
+ const [sessions, setSessions] = React.useState([]);
3014
+ const [isLoading, setIsLoading] = React.useState(false);
3015
+ const [error, setError] = React.useState(null);
3016
+ const [page, setPage] = React.useState(0);
3017
+ const [hasNext, setHasNext] = React.useState(false);
3018
+ const inFlightRef = React.useRef(null);
3019
+ const configRef = React.useRef(config);
140
3020
  configRef.current = config;
141
- const fetchPage = react.useCallback(
3021
+ const fetchPage = React.useCallback(
142
3022
  async (pageIndex, replace) => {
143
3023
  if (!isReady) return;
144
3024
  inFlightRef.current?.abort();
@@ -167,12 +3047,12 @@ function useSessionHistory(config, options = {}, optimisticActivity) {
167
3047
  },
168
3048
  [isReady, pageSize]
169
3049
  );
170
- const refresh = react.useCallback(() => fetchPage(0, true), [fetchPage]);
171
- const loadMore = react.useCallback(async () => {
3050
+ const refresh = React.useCallback(() => fetchPage(0, true), [fetchPage]);
3051
+ const loadMore = React.useCallback(async () => {
172
3052
  if (!hasNext || isLoading) return;
173
3053
  await fetchPage(page + 1, false);
174
3054
  }, [fetchPage, hasNext, isLoading, page]);
175
- react.useEffect(() => {
3055
+ React.useEffect(() => {
176
3056
  if (!optimisticActivity) return;
177
3057
  setSessions((prev) => {
178
3058
  const next = [...prev];
@@ -213,7 +3093,7 @@ function useSessionHistory(config, options = {}, optimisticActivity) {
213
3093
  config.workflow.version,
214
3094
  optimisticActivity
215
3095
  ]);
216
- react.useEffect(() => {
3096
+ React.useEffect(() => {
217
3097
  if (!isReady) return;
218
3098
  void fetchPage(0, true);
219
3099
  return () => {
@@ -243,11 +3123,11 @@ function useSessionHistory(config, options = {}, optimisticActivity) {
243
3123
  }
244
3124
  var MOBILE_BREAKPOINT = 640;
245
3125
  function useIsMobile() {
246
- const [isMobile, setIsMobile] = react.useState(() => {
3126
+ const [isMobile, setIsMobile] = React.useState(() => {
247
3127
  if (typeof window === "undefined") return false;
248
3128
  return window.innerWidth < MOBILE_BREAKPOINT;
249
3129
  });
250
- react.useEffect(() => {
3130
+ React.useEffect(() => {
251
3131
  if (typeof window === "undefined") return;
252
3132
  const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
253
3133
  const handler = (e) => setIsMobile(e.matches);
@@ -281,18 +3161,20 @@ function SessionHistorySidebar({
281
3161
  options,
282
3162
  activeSessionId,
283
3163
  loadingSessionId,
284
- streamingSessionIds,
285
3164
  recentlyCompletedSessionIds,
286
3165
  optimisticActivity,
287
3166
  onSelectSession,
3167
+ onNewSession,
3168
+ newSessionDisabled = false,
288
3169
  mobileOpen,
289
3170
  onMobileOpenChange
290
3171
  }) {
291
3172
  const isMobile = useIsMobile();
292
- const mobileThemeSourceRef = react.useRef(null);
293
- const [mobilePortalThemeStyle, setMobilePortalThemeStyle] = react.useState({});
3173
+ const mobileThemeSourceRef = React.useRef(null);
3174
+ const [mobilePortalThemeStyle, setMobilePortalThemeStyle] = React.useState({});
294
3175
  const history = useSessionHistory(config, options, optimisticActivity);
295
- react.useEffect(() => {
3176
+ const showNewSessionButton = options.showNewSessionButton === true;
3177
+ React.useEffect(() => {
296
3178
  if (!isMobile) return;
297
3179
  setMobilePortalThemeStyle(readPortalThemeStyle(mobileThemeSourceRef.current));
298
3180
  }, [isMobile, mobileOpen]);
@@ -318,10 +3200,12 @@ function SessionHistorySidebar({
318
3200
  history,
319
3201
  activeSessionId,
320
3202
  loadingSessionId,
321
- streamingSessionIds,
322
3203
  recentlyCompletedSessionIds,
323
- onSelectSession: (s3) => {
324
- onSelectSession(s3);
3204
+ showNewSessionButton,
3205
+ onNewSession,
3206
+ newSessionDisabled,
3207
+ onSelectSession: (s9) => {
3208
+ onSelectSession(s9);
325
3209
  onMobileOpenChange(false);
326
3210
  }
327
3211
  }
@@ -334,8 +3218,10 @@ function SessionHistorySidebar({
334
3218
  history,
335
3219
  activeSessionId,
336
3220
  loadingSessionId,
337
- streamingSessionIds,
338
3221
  recentlyCompletedSessionIds,
3222
+ showNewSessionButton,
3223
+ onNewSession,
3224
+ newSessionDisabled,
339
3225
  onSelectSession
340
3226
  }
341
3227
  );
@@ -344,15 +3230,18 @@ function DesktopSidebar({
344
3230
  history,
345
3231
  activeSessionId,
346
3232
  loadingSessionId,
347
- streamingSessionIds,
348
3233
  recentlyCompletedSessionIds,
3234
+ showNewSessionButton,
3235
+ onNewSession,
3236
+ newSessionDisabled,
349
3237
  onSelectSession
350
3238
  }) {
351
3239
  const { width, setWidth, collapsed, setCollapsed, minWidth, maxWidth } = history;
352
- const dragStateRef = react.useRef(null);
353
- const [isDragging, setIsDragging] = react.useState(false);
354
- const [handleHover, setHandleHover] = react.useState(false);
355
- const onResizeStart = react.useCallback(
3240
+ const reduceMotion = framerMotion.useReducedMotion();
3241
+ const dragStateRef = React.useRef(null);
3242
+ const [isDragging, setIsDragging] = React.useState(false);
3243
+ const [handleHover, setHandleHover] = React.useState(false);
3244
+ const onResizeStart = React.useCallback(
356
3245
  (e) => {
357
3246
  e.preventDefault();
358
3247
  dragStateRef.current = { startX: e.clientX, startWidth: width };
@@ -360,7 +3249,7 @@ function DesktopSidebar({
360
3249
  },
361
3250
  [width]
362
3251
  );
363
- react.useEffect(() => {
3252
+ React.useEffect(() => {
364
3253
  if (!isDragging) return;
365
3254
  const onMove = (e) => {
366
3255
  const st = dragStateRef.current;
@@ -380,25 +3269,42 @@ function DesktopSidebar({
380
3269
  window.removeEventListener("pointercancel", onEnd);
381
3270
  };
382
3271
  }, [isDragging, setWidth]);
3272
+ const handleActive = handleHover || isDragging;
3273
+ const sidebarEnter = reduceMotion ? { duration: 0.1, ease: "easeOut" } : { duration: 0.16, ease: [0.25, 0.46, 0.45, 0.94] };
383
3274
  if (collapsed) {
384
3275
  return /* @__PURE__ */ jsxRuntime.jsx(
385
- CollapsedButton,
3276
+ framerMotion.motion.div,
386
3277
  {
387
- onExpand: () => setCollapsed(false),
388
- history,
389
- activeSessionId,
390
- loadingSessionId,
391
- streamingSessionIds,
392
- recentlyCompletedSessionIds,
393
- onSelectSession
394
- }
3278
+ className: "payman-sidebar-collapsed-mount",
3279
+ initial: reduceMotion ? false : { opacity: 0, scale: 0.96 },
3280
+ animate: { opacity: 1, scale: 1 },
3281
+ transition: sidebarEnter,
3282
+ style: { alignSelf: "flex-start" },
3283
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3284
+ CollapsedButton,
3285
+ {
3286
+ onExpand: () => setCollapsed(false),
3287
+ history,
3288
+ activeSessionId,
3289
+ loadingSessionId,
3290
+ recentlyCompletedSessionIds,
3291
+ showNewSessionButton,
3292
+ onNewSession,
3293
+ newSessionDisabled,
3294
+ onSelectSession
3295
+ }
3296
+ )
3297
+ },
3298
+ "payman-sidebar-desktop-collapsed"
395
3299
  );
396
3300
  }
397
- const handleActive = handleHover || isDragging;
398
3301
  return /* @__PURE__ */ jsxRuntime.jsxs(
399
- "aside",
3302
+ framerMotion.motion.aside,
400
3303
  {
401
3304
  className: "payman-sidebar payman-sidebar-desktop",
3305
+ initial: reduceMotion ? false : { opacity: 0 },
3306
+ animate: { opacity: 1 },
3307
+ transition: sidebarEnter,
402
3308
  style: {
403
3309
  width,
404
3310
  minWidth,
@@ -409,17 +3315,24 @@ function DesktopSidebar({
409
3315
  display: "flex",
410
3316
  flexDirection: "column",
411
3317
  overflow: "hidden",
412
- transition: isDragging ? "none" : "width 140ms ease"
3318
+ transition: isDragging ? "none" : "width 100ms ease"
413
3319
  },
414
3320
  children: [
415
- /* @__PURE__ */ jsxRuntime.jsx(SidebarHeader, { onCollapse: () => setCollapsed(true) }),
3321
+ /* @__PURE__ */ jsxRuntime.jsx(
3322
+ SidebarTopBar,
3323
+ {
3324
+ onCollapse: () => setCollapsed(true),
3325
+ showNewSessionButton,
3326
+ onNewSession,
3327
+ newSessionDisabled
3328
+ }
3329
+ ),
416
3330
  /* @__PURE__ */ jsxRuntime.jsx(
417
3331
  SessionList,
418
3332
  {
419
3333
  history,
420
3334
  activeSessionId,
421
3335
  loadingSessionId,
422
- streamingSessionIds,
423
3336
  recentlyCompletedSessionIds,
424
3337
  onSelectSession
425
3338
  }
@@ -457,28 +3370,32 @@ function DesktopSidebar({
457
3370
  borderRadius: 999,
458
3371
  background: handleActive ? "var(--payman-v2-text-3)" : "transparent",
459
3372
  opacity: handleActive ? 1 : 0,
460
- transition: "opacity 140ms ease, background 140ms ease"
3373
+ transition: "opacity 100ms ease, background 100ms ease"
461
3374
  }
462
3375
  }
463
3376
  )
464
3377
  }
465
3378
  )
466
3379
  ]
467
- }
3380
+ },
3381
+ "payman-sidebar-desktop-expanded"
468
3382
  );
469
3383
  }
470
- var POPOVER_LEAVE_DELAY_MS = 200;
3384
+ var POPOVER_LEAVE_DELAY_MS = 220;
471
3385
  function CollapsedButton({
472
3386
  onExpand,
473
3387
  history,
474
3388
  activeSessionId,
475
3389
  loadingSessionId,
476
- streamingSessionIds,
477
3390
  recentlyCompletedSessionIds,
3391
+ showNewSessionButton,
3392
+ onNewSession,
3393
+ newSessionDisabled,
478
3394
  onSelectSession
479
3395
  }) {
480
- const [hoverOpen, setHoverOpen] = react.useState(false);
481
- const leaveTimerRef = react.useRef(null);
3396
+ const reduceMotion = framerMotion.useReducedMotion();
3397
+ const [hoverOpen, setHoverOpen] = React.useState(false);
3398
+ const leaveTimerRef = React.useRef(null);
482
3399
  const openPopover = () => {
483
3400
  if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
484
3401
  setHoverOpen(true);
@@ -490,7 +3407,7 @@ function CollapsedButton({
490
3407
  POPOVER_LEAVE_DELAY_MS
491
3408
  );
492
3409
  };
493
- react.useEffect(
3410
+ React.useEffect(
494
3411
  () => () => {
495
3412
  if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
496
3413
  },
@@ -505,22 +3422,29 @@ function CollapsedButton({
505
3422
  style: { position: "relative", alignSelf: "flex-start" },
506
3423
  children: [
507
3424
  /* @__PURE__ */ jsxRuntime.jsx(
508
- "button",
3425
+ framerMotion.motion.button,
509
3426
  {
510
3427
  type: "button",
511
3428
  "aria-label": "Expand sidebar",
512
3429
  title: "Expand sidebar",
513
3430
  onClick: onExpand,
514
3431
  className: "payman-sidebar-collapsed-button",
3432
+ whileHover: reduceMotion ? void 0 : { scale: 1.04 },
3433
+ whileTap: reduceMotion ? void 0 : { scale: 0.97 },
3434
+ transition: { type: "spring", stiffness: 620, damping: 32 },
515
3435
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftOpen, { size: 16 })
516
3436
  }
517
3437
  ),
518
- hoverOpen && /* @__PURE__ */ jsxRuntime.jsxs(
519
- "div",
3438
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: hoverOpen && /* @__PURE__ */ jsxRuntime.jsxs(
3439
+ framerMotion.motion.div,
520
3440
  {
521
3441
  className: "payman-sidebar-popover",
522
3442
  onMouseEnter: openPopover,
523
3443
  onMouseLeave: schedulePopoverClose,
3444
+ initial: reduceMotion ? { opacity: 0 } : { opacity: 0, x: -6, scale: 0.98 },
3445
+ animate: reduceMotion ? { opacity: 1 } : { opacity: 1, x: 0, scale: 1 },
3446
+ exit: reduceMotion ? { opacity: 0 } : { opacity: 0, x: -4, scale: 0.99 },
3447
+ transition: reduceMotion ? { duration: 0.08 } : { duration: 0.14, ease: [0.25, 0.46, 0.45, 0.94] },
524
3448
  style: {
525
3449
  position: "absolute",
526
3450
  left: "calc(100% + 6px)",
@@ -530,27 +3454,35 @@ function CollapsedButton({
530
3454
  zIndex: 50,
531
3455
  display: "flex",
532
3456
  flexDirection: "column",
533
- overflow: "hidden"
3457
+ overflow: "hidden",
3458
+ transformOrigin: "left center"
534
3459
  },
535
3460
  children: [
536
- /* @__PURE__ */ jsxRuntime.jsx(SidebarHeader, {}),
3461
+ /* @__PURE__ */ jsxRuntime.jsx(
3462
+ SidebarTopBar,
3463
+ {
3464
+ showNewSessionButton,
3465
+ onNewSession,
3466
+ newSessionDisabled
3467
+ }
3468
+ ),
537
3469
  /* @__PURE__ */ jsxRuntime.jsx(
538
3470
  SessionList,
539
3471
  {
540
3472
  history,
541
3473
  activeSessionId,
542
3474
  loadingSessionId,
543
- streamingSessionIds,
544
3475
  recentlyCompletedSessionIds,
545
- onSelectSession: (s3) => {
546
- onSelectSession(s3);
3476
+ onSelectSession: (s9) => {
3477
+ onSelectSession(s9);
547
3478
  setHoverOpen(false);
548
3479
  }
549
3480
  }
550
3481
  )
551
3482
  ]
552
- }
553
- )
3483
+ },
3484
+ "payman-sidebar-hover-popover"
3485
+ ) })
554
3486
  ]
555
3487
  }
556
3488
  );
@@ -562,10 +3494,16 @@ function MobileDrawer({
562
3494
  history,
563
3495
  activeSessionId,
564
3496
  loadingSessionId,
565
- streamingSessionIds,
566
3497
  recentlyCompletedSessionIds,
3498
+ showNewSessionButton,
3499
+ onNewSession,
3500
+ newSessionDisabled,
567
3501
  onSelectSession
568
3502
  }) {
3503
+ const handleNewSession = onNewSession ? () => {
3504
+ onOpenChange(false);
3505
+ onNewSession();
3506
+ } : void 0;
569
3507
  if (!open || typeof document === "undefined") return null;
570
3508
  return reactDom.createPortal(
571
3509
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -596,14 +3534,22 @@ function MobileDrawer({
596
3534
  },
597
3535
  children: [
598
3536
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-sheet-grabber", "aria-hidden": true }),
599
- /* @__PURE__ */ jsxRuntime.jsx(SidebarHeader, { onCollapse: () => onOpenChange(false), mobile: true }),
3537
+ /* @__PURE__ */ jsxRuntime.jsx(
3538
+ SidebarTopBar,
3539
+ {
3540
+ onCollapse: () => onOpenChange(false),
3541
+ mobile: true,
3542
+ showNewSessionButton,
3543
+ onNewSession: handleNewSession,
3544
+ newSessionDisabled
3545
+ }
3546
+ ),
600
3547
  /* @__PURE__ */ jsxRuntime.jsx(
601
3548
  SessionList,
602
3549
  {
603
3550
  history,
604
3551
  activeSessionId,
605
3552
  loadingSessionId,
606
- streamingSessionIds,
607
3553
  recentlyCompletedSessionIds,
608
3554
  onSelectSession
609
3555
  }
@@ -616,12 +3562,48 @@ function MobileDrawer({
616
3562
  document.body
617
3563
  );
618
3564
  }
3565
+ function SidebarTopBar({
3566
+ onCollapse,
3567
+ mobile = false,
3568
+ showNewSessionButton = false,
3569
+ onNewSession,
3570
+ newSessionDisabled = false
3571
+ }) {
3572
+ const shouldShowNewSession = showNewSessionButton && onNewSession != null;
3573
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3574
+ "div",
3575
+ {
3576
+ className: cn(
3577
+ "payman-sidebar-topbar",
3578
+ shouldShowNewSession && "has-new-session"
3579
+ ),
3580
+ children: [
3581
+ /* @__PURE__ */ jsxRuntime.jsx(SidebarHeader, { onCollapse, mobile }),
3582
+ shouldShowNewSession && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-new-session-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs(
3583
+ "button",
3584
+ {
3585
+ type: "button",
3586
+ className: "payman-sidebar-new-session-button",
3587
+ onClick: onNewSession,
3588
+ disabled: newSessionDisabled,
3589
+ "aria-label": "New Session",
3590
+ title: "New Session",
3591
+ children: [
3592
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { size: 13, strokeWidth: 2.4 }),
3593
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "New Session" })
3594
+ ]
3595
+ }
3596
+ ) })
3597
+ ]
3598
+ }
3599
+ );
3600
+ }
619
3601
  function SidebarHeader({
620
3602
  onCollapse,
621
3603
  mobile = false
622
3604
  }) {
623
3605
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-sidebar-header", children: [
624
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Recent sessions" }),
3606
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-sidebar-header-title", children: "Recent sessions" }),
625
3607
  onCollapse && /* @__PURE__ */ jsxRuntime.jsx(
626
3608
  "button",
627
3609
  {
@@ -629,7 +3611,8 @@ function SidebarHeader({
629
3611
  "aria-label": mobile ? "Close recent sessions" : "Collapse sidebar",
630
3612
  title: mobile ? "Close recent sessions" : "Collapse sidebar",
631
3613
  onClick: onCollapse,
632
- children: mobile ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftClose, { size: 14 })
3614
+ className: "payman-sidebar-header-icon-button",
3615
+ children: mobile ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 15 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftClose, { size: 13 })
633
3616
  }
634
3617
  )
635
3618
  ] });
@@ -638,14 +3621,13 @@ function SessionList({
638
3621
  history,
639
3622
  activeSessionId,
640
3623
  loadingSessionId,
641
- streamingSessionIds,
642
3624
  recentlyCompletedSessionIds,
643
3625
  onSelectSession
644
3626
  }) {
645
- const listRef = react.useRef(null);
3627
+ const listRef = React.useRef(null);
646
3628
  const { sessions, hasNext, isLoading, error, loadMore, isReady, refresh } = history;
647
- const formattedNow = react.useMemo(() => /* @__PURE__ */ new Date(), []);
648
- const sessionGroups = react.useMemo(
3629
+ const formattedNow = React.useMemo(() => /* @__PURE__ */ new Date(), []);
3630
+ const sessionGroups = React.useMemo(
649
3631
  () => groupSessionsByDate(sessions, formattedNow),
650
3632
  [sessions, formattedNow]
651
3633
  );
@@ -669,17 +3651,16 @@ function SessionList({
669
3651
  "aria-label": group.label,
670
3652
  children: [
671
3653
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-group-label", children: group.label }),
672
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-group-items", children: group.sessions.map((s3) => /* @__PURE__ */ jsxRuntime.jsx(
3654
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-sidebar-group-items", children: group.sessions.map((s9) => /* @__PURE__ */ jsxRuntime.jsx(
673
3655
  SessionRow,
674
3656
  {
675
- session: s3,
676
- isActive: s3.sessionId === activeSessionId,
677
- isLoading: s3.sessionId === loadingSessionId,
678
- isStreaming: streamingSessionIds?.has(s3.sessionId) ?? false,
679
- isRecentlyCompleted: recentlyCompletedSessionIds?.has(s3.sessionId) ?? false,
3657
+ session: s9,
3658
+ isActive: s9.sessionId === activeSessionId,
3659
+ isLoading: s9.sessionId === loadingSessionId,
3660
+ isRecentlyCompleted: recentlyCompletedSessionIds?.has(s9.sessionId) ?? false,
680
3661
  onSelect: onSelectSession
681
3662
  },
682
- s3.sessionId
3663
+ s9.sessionId
683
3664
  )) })
684
3665
  ]
685
3666
  },
@@ -708,26 +3689,12 @@ function SessionRow({
708
3689
  session,
709
3690
  isActive,
710
3691
  isLoading,
711
- isStreaming,
712
3692
  isRecentlyCompleted,
713
3693
  onSelect
714
3694
  }) {
715
3695
  let statusNode = null;
716
3696
  let statusLabel;
717
- if (isStreaming) {
718
- statusNode = /* @__PURE__ */ jsxRuntime.jsxs(
719
- "span",
720
- {
721
- className: "payman-sidebar-row-status payman-sidebar-row-status-live",
722
- "aria-hidden": true,
723
- children: [
724
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-sidebar-row-live-dot" }),
725
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-sidebar-row-live-track" })
726
- ]
727
- }
728
- );
729
- statusLabel = "Running";
730
- } else if (isRecentlyCompleted) {
3697
+ if (isRecentlyCompleted) {
731
3698
  statusNode = /* @__PURE__ */ jsxRuntime.jsx(
732
3699
  "span",
733
3700
  {
@@ -748,7 +3715,7 @@ function SessionRow({
748
3715
  onClick: () => onSelect(session),
749
3716
  className: cn("payman-sidebar-row", isActive && "is-active"),
750
3717
  title: statusLabel ? `${session.sessionTitle || "Untitled session"} \u2014 ${statusLabel}` : session.sessionTitle,
751
- "aria-busy": isLoading || isStreaming || void 0,
3718
+ "aria-busy": isLoading || void 0,
752
3719
  children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "payman-sidebar-row-main", children: [
753
3720
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-sidebar-row-title", children: session.sessionTitle || "Untitled session" }),
754
3721
  statusNode
@@ -837,10 +3804,17 @@ function groupSessionsByDate(sessions, now) {
837
3804
  }
838
3805
  return orderedGroups;
839
3806
  }
3807
+ function titleCaseDateHeading(label) {
3808
+ return label.replace(/[\p{L}\p{M}]+/gu, (word) => {
3809
+ const lower = word.toLocaleLowerCase(void 0);
3810
+ const first = lower.charAt(0).toLocaleUpperCase(void 0);
3811
+ return first + lower.slice(1);
3812
+ });
3813
+ }
840
3814
  function getSessionDateBucket(iso, now) {
841
3815
  const date = new Date(iso);
842
3816
  if (Number.isNaN(date.getTime())) {
843
- return { key: "older", label: "Older" };
3817
+ return { key: "older", label: titleCaseDateHeading("Older") };
844
3818
  }
845
3819
  const dayKey = [
846
3820
  date.getFullYear(),
@@ -852,22 +3826,26 @@ function getSessionDateBucket(iso, now) {
852
3826
  const dayDiff = Math.round(
853
3827
  (today.getTime() - sessionDay.getTime()) / 864e5
854
3828
  );
855
- if (dayDiff <= 0) return { key: dayKey, label: "Today" };
856
- if (dayDiff === 1) return { key: dayKey, label: "Yesterday" };
3829
+ if (dayDiff <= 0) return { key: dayKey, label: titleCaseDateHeading("Today") };
3830
+ if (dayDiff === 1) return { key: dayKey, label: titleCaseDateHeading("Yesterday") };
857
3831
  if (dayDiff < 7) {
858
3832
  return {
859
3833
  key: dayKey,
860
- label: date.toLocaleDateString(void 0, { weekday: "long" })
3834
+ label: titleCaseDateHeading(
3835
+ date.toLocaleDateString(void 0, { weekday: "long" })
3836
+ )
861
3837
  };
862
3838
  }
863
3839
  const sameYear = date.getFullYear() === now.getFullYear();
864
3840
  return {
865
3841
  key: dayKey,
866
- label: date.toLocaleDateString(void 0, {
867
- month: "short",
868
- day: "numeric",
869
- year: sameYear ? void 0 : "numeric"
870
- })
3842
+ label: titleCaseDateHeading(
3843
+ date.toLocaleDateString(void 0, {
3844
+ month: "long",
3845
+ day: "numeric",
3846
+ year: sameYear ? void 0 : "numeric"
3847
+ })
3848
+ )
871
3849
  };
872
3850
  }
873
3851
  function ChatHeader({
@@ -877,7 +3855,7 @@ function ChatHeader({
877
3855
  showResetSession = false,
878
3856
  children
879
3857
  }) {
880
- const [copiedSessionId, setCopiedSessionId] = react.useState(false);
3858
+ const [copiedSessionId, setCopiedSessionId] = React.useState(false);
881
3859
  const handleCopySessionId = () => {
882
3860
  if (sessionId && onCopySessionId) {
883
3861
  reactNative.Clipboard.setString(sessionId);
@@ -934,203 +3912,8 @@ var styles2 = reactNative.StyleSheet.create({
934
3912
 
935
3913
  // src/assets/payman-mono-crop-blue.png
936
3914
  var payman_mono_crop_blue_default = "data:image/png;base64,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";
937
- var DEFAULT_MAX_LENGTH = 6;
938
- var MAX_SUPPORTED_LENGTH = 12;
939
- var OTP_ERROR_BORDER = "#ef4444";
940
- var COMPLETE_PULSE_MS = 350;
941
- var SHAKE_MS = 400;
942
- function OtpInput({
943
- value,
944
- onChange,
945
- maxLength,
946
- disabled = false,
947
- error = false
948
- }) {
949
- const inputRefs = react.useRef([]);
950
- const shakeAnim = react.useRef(new reactNative.Animated.Value(0)).current;
951
- const scaleAnim = react.useRef(new reactNative.Animated.Value(1)).current;
952
- const prevLenRef = react.useRef(0);
953
- const [internalError, setInternalError] = react.useState(false);
954
- const safeMaxLength = Number.isInteger(maxLength) && maxLength > 0 ? Math.min(maxLength, MAX_SUPPORTED_LENGTH) : DEFAULT_MAX_LENGTH;
955
- const digits = value.split("").concat(Array(safeMaxLength).fill("")).slice(0, safeMaxLength);
956
- const isFull = value.length === safeMaxLength && /^\d+$/.test(value);
957
- react.useEffect(() => {
958
- if (!disabled) {
959
- inputRefs.current[0]?.focus();
960
- }
961
- }, [disabled]);
962
- react.useEffect(() => {
963
- if (!error) {
964
- setInternalError(false);
965
- return;
966
- }
967
- setInternalError(true);
968
- reactNative.Animated.sequence([
969
- reactNative.Animated.timing(shakeAnim, { toValue: 1, duration: SHAKE_MS, useNativeDriver: true }),
970
- reactNative.Animated.timing(shakeAnim, { toValue: 0, duration: 0, useNativeDriver: true })
971
- ]).start();
972
- }, [error, shakeAnim]);
973
- react.useEffect(() => {
974
- if (isFull && prevLenRef.current < safeMaxLength) {
975
- reactNative.Animated.sequence([
976
- reactNative.Animated.timing(scaleAnim, { toValue: 1.04, duration: COMPLETE_PULSE_MS * 0.4, useNativeDriver: true }),
977
- reactNative.Animated.timing(scaleAnim, { toValue: 1, duration: COMPLETE_PULSE_MS * 0.6, useNativeDriver: true })
978
- ]).start();
979
- }
980
- prevLenRef.current = value.length;
981
- }, [isFull, value.length, safeMaxLength, scaleAnim]);
982
- const shakeTranslate = shakeAnim.interpolate({
983
- inputRange: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 0.9, 1],
984
- outputRange: [0, -6, 5, -4, 3, -2, 1, 0]
985
- });
986
- const focusInput = (index) => {
987
- if (index >= 0 && index < safeMaxLength) {
988
- inputRefs.current[index]?.focus();
989
- }
990
- };
991
- const updateValue = (newDigits) => {
992
- onChange(newDigits.join("").slice(0, safeMaxLength));
993
- };
994
- const handleChange = (index, text) => {
995
- const cleaned = text.replace(/\D/g, "");
996
- if (cleaned.length > 1) {
997
- const pasted = cleaned.slice(0, safeMaxLength);
998
- const newDigits2 = pasted.split("").concat(Array(safeMaxLength).fill("")).slice(0, safeMaxLength);
999
- updateValue(newDigits2);
1000
- focusInput(Math.min(pasted.length, safeMaxLength - 1));
1001
- return;
1002
- }
1003
- const char = cleaned.slice(-1);
1004
- const newDigits = [...digits];
1005
- newDigits[index] = char;
1006
- updateValue(newDigits);
1007
- if (char && index < safeMaxLength - 1) {
1008
- focusInput(index + 1);
1009
- }
1010
- };
1011
- const handleKeyPress = (index, key) => {
1012
- if (key === "Backspace") {
1013
- if (digits[index]) {
1014
- const newDigits = [...digits];
1015
- newDigits[index] = "";
1016
- updateValue(newDigits);
1017
- } else if (index > 0) {
1018
- const newDigits = [...digits];
1019
- newDigits[index - 1] = "";
1020
- updateValue(newDigits);
1021
- focusInput(index - 1);
1022
- }
1023
- }
1024
- };
1025
- return /* @__PURE__ */ jsxRuntime.jsx(
1026
- reactNative.Animated.View,
1027
- {
1028
- style: [
1029
- s.container,
1030
- { transform: [{ translateX: shakeTranslate }, { scale: scaleAnim }] }
1031
- ],
1032
- children: digits.map((digit, i) => /* @__PURE__ */ jsxRuntime.jsx(
1033
- reactNative.TextInput,
1034
- {
1035
- ref: (el) => {
1036
- inputRefs.current[i] = el;
1037
- },
1038
- value: digit,
1039
- editable: !disabled,
1040
- keyboardType: "number-pad",
1041
- maxLength: 1,
1042
- onChangeText: (text) => handleChange(i, text),
1043
- onKeyPress: ({ nativeEvent }) => handleKeyPress(i, nativeEvent.key),
1044
- onFocus: () => inputRefs.current[i]?.setNativeProps({ selection: { start: 0, end: 1 } }),
1045
- style: [
1046
- s.input,
1047
- disabled && s.inputDisabled,
1048
- internalError && s.inputError
1049
- ],
1050
- accessibilityLabel: `Digit ${i + 1}`
1051
- },
1052
- i
1053
- ))
1054
- }
1055
- );
1056
- }
1057
- var PAYMAN_OTP = {
1058
- bg: "#FFFFFF",
1059
- border: "rgba(0,0,0,0.1)",
1060
- fg: "#18181b",
1061
- disabledBg: "rgba(0,0,0,0.03)"
1062
- };
1063
- var s = reactNative.StyleSheet.create({
1064
- container: { flexDirection: "row", gap: 8, justifyContent: "center" },
1065
- input: {
1066
- width: 44,
1067
- height: 50,
1068
- textAlign: "center",
1069
- fontSize: 20,
1070
- fontWeight: "600",
1071
- borderWidth: 1,
1072
- borderColor: PAYMAN_OTP.border,
1073
- borderRadius: 10,
1074
- color: PAYMAN_OTP.fg,
1075
- backgroundColor: PAYMAN_OTP.bg
1076
- },
1077
- inputDisabled: { backgroundColor: PAYMAN_OTP.disabledBg, opacity: 0.5 },
1078
- inputError: {
1079
- borderColor: OTP_ERROR_BORDER,
1080
- borderWidth: 1.5
1081
- }
1082
- });
1083
-
1084
- // src/components/UserActionModal/constants.ts
1085
- var BUTTON_LABELS = {
1086
- /** Link-style actions (new layout) */
1087
- RESEND_CODE: "Resend OTP",
1088
- CANCEL_TRANSFER: "Cancel Payment",
1089
- /** Short cancel label for payee approval flows */
1090
- CANCEL: "Cancel"
1091
- };
1092
- var RESEND_OTP_COOLDOWN_SECONDS = 30;
1093
- var DEFAULT_OTP_MAX_LENGTH = 6;
1094
- var MIN_OTP_MAX_LENGTH = 1;
1095
- var MAX_OTP_MAX_LENGTH = 12;
1096
- var ACTION_PENDING_TIMEOUT_MS = 15e3;
1097
- var MODAL_CONTENT = {
1098
- LOADING_APPROVE: "Verifying...",
1099
- LOADING_REJECT: "Rejecting...",
1100
- LOADING_RESEND: "Resending...",
1101
- RESEND_AVAILABLE_IN: "Resend OTP in",
1102
- SECURED_BY_PREFIX: "Secured by",
1103
- SECURED_BY_BRAND: "Payman"
1104
- };
1105
-
1106
- // src/components/UserActionModal/utils.ts
1107
- function getOtpSchemaFromRequest(schema) {
1108
- const properties = schema?.properties;
1109
- const otp = properties?.otp;
1110
- const maxLengthRaw = otp?.maxLength;
1111
- const parsedMaxLength = Number.isInteger(maxLengthRaw) ? Number(maxLengthRaw) : DEFAULT_OTP_MAX_LENGTH;
1112
- const clampedMaxLength = Math.min(
1113
- MAX_OTP_MAX_LENGTH,
1114
- Math.max(MIN_OTP_MAX_LENGTH, parsedMaxLength)
1115
- );
1116
- return {
1117
- maxLength: clampedMaxLength
1118
- };
1119
- }
1120
- function formatAmountForDisplay(amount) {
1121
- const normalized = amount.replace(/,/g, "").trim();
1122
- const n = Number(normalized);
1123
- if (!Number.isFinite(n)) {
1124
- return amount.startsWith("$") ? amount : `$${amount}`;
1125
- }
1126
- return new Intl.NumberFormat("en-US", {
1127
- style: "currency",
1128
- currency: "USD"
1129
- }).format(n);
1130
- }
1131
3915
  var PAYMAN_BRAND_GREEN = "#0A3B44";
1132
3916
  var PAYMAN = {
1133
- foreground: "#18181b",
1134
3917
  mutedForeground: "#71717a",
1135
3918
  border: "#e4e4e7",
1136
3919
  card: "#ffffff",
@@ -1139,7 +3922,7 @@ var PAYMAN = {
1139
3922
  var { width: SCREEN_WIDTH } = reactNative.Dimensions.get("window");
1140
3923
  var DIALOG_MAX_WIDTH = Math.min(440, SCREEN_WIDTH * 0.94);
1141
3924
  var DIALOG_PADDING = 28;
1142
- var OTP_ERROR_FLASH_MS = 600;
3925
+ var OTP_ERROR_FLASH_MS2 = 600;
1143
3926
  function UserActionModal({
1144
3927
  isOpen,
1145
3928
  userActionRequest,
@@ -1148,17 +3931,17 @@ function UserActionModal({
1148
3931
  onResend,
1149
3932
  clearOtpTrigger
1150
3933
  }) {
1151
- const [otp, setOtp] = react.useState("");
1152
- const [actionType, setActionType] = react.useState(null);
1153
- const [isSubmitting, setIsSubmitting] = react.useState(false);
1154
- const [resendCooldownRemaining, setResendCooldownRemaining] = react.useState(0);
1155
- const [keyboardVisible, setKeyboardVisible] = react.useState(false);
1156
- const [otpError, setOtpError] = react.useState(false);
1157
- const lastAutoSubmittedRef = react.useRef("");
1158
- const submitInFlightRef = react.useRef(false);
1159
- const submitGenerationRef = react.useRef(0);
3934
+ const [otp, setOtp] = React.useState("");
3935
+ const [actionType, setActionType] = React.useState(null);
3936
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
3937
+ const [resendCooldownRemaining, setResendCooldownRemaining] = React.useState(0);
3938
+ const [keyboardVisible, setKeyboardVisible] = React.useState(false);
3939
+ const [otpError, setOtpError] = React.useState(false);
3940
+ const lastAutoSubmittedRef = React.useRef("");
3941
+ const submitInFlightRef = React.useRef(false);
3942
+ const submitGenerationRef = React.useRef(0);
1160
3943
  const schema = getOtpSchemaFromRequest(userActionRequest?.requestedSchema);
1161
- react.useEffect(() => {
3944
+ React.useEffect(() => {
1162
3945
  const show = reactNative.Keyboard.addListener(
1163
3946
  reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
1164
3947
  () => setKeyboardVisible(true)
@@ -1172,11 +3955,11 @@ function UserActionModal({
1172
3955
  hide.remove();
1173
3956
  };
1174
3957
  }, []);
1175
- const resetActionState = react.useCallback(() => {
3958
+ const resetActionState = React.useCallback(() => {
1176
3959
  setIsSubmitting(false);
1177
3960
  setActionType(null);
1178
3961
  }, []);
1179
- react.useEffect(() => {
3962
+ React.useEffect(() => {
1180
3963
  if (isOpen) {
1181
3964
  setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
1182
3965
  } else {
@@ -1189,7 +3972,7 @@ function UserActionModal({
1189
3972
  submitGenerationRef.current += 1;
1190
3973
  }
1191
3974
  }, [isOpen, resetActionState]);
1192
- react.useEffect(() => {
3975
+ React.useEffect(() => {
1193
3976
  if (resendCooldownRemaining <= 0) return;
1194
3977
  const timer = setTimeout(
1195
3978
  () => setResendCooldownRemaining((prev) => prev - 1),
@@ -1197,18 +3980,18 @@ function UserActionModal({
1197
3980
  );
1198
3981
  return () => clearTimeout(timer);
1199
3982
  }, [resendCooldownRemaining]);
1200
- react.useEffect(() => {
3983
+ React.useEffect(() => {
1201
3984
  if (clearOtpTrigger > 0) {
1202
3985
  setOtpError(true);
1203
3986
  const t = setTimeout(() => {
1204
3987
  setOtpError(false);
1205
3988
  setOtp("");
1206
3989
  resetActionState();
1207
- }, OTP_ERROR_FLASH_MS);
3990
+ }, OTP_ERROR_FLASH_MS2);
1208
3991
  return () => clearTimeout(t);
1209
3992
  }
1210
3993
  }, [clearOtpTrigger, resetActionState]);
1211
- react.useEffect(() => {
3994
+ React.useEffect(() => {
1212
3995
  if (!isOpen || !isSubmitting) return;
1213
3996
  if (actionType !== "approve" && actionType !== "reject") return;
1214
3997
  const timeout = setTimeout(
@@ -1217,7 +4000,7 @@ function UserActionModal({
1217
4000
  );
1218
4001
  return () => clearTimeout(timeout);
1219
4002
  }, [isOpen, isSubmitting, actionType, resetActionState]);
1220
- react.useEffect(() => {
4003
+ React.useEffect(() => {
1221
4004
  if (!isOpen || !userActionRequest) return;
1222
4005
  if (otp.length !== schema.maxLength || !/^\d+$/.test(otp)) {
1223
4006
  return;
@@ -1250,7 +4033,7 @@ function UserActionModal({
1250
4033
  onApprove,
1251
4034
  resetActionState
1252
4035
  ]);
1253
- const handleReject = react.useCallback(async () => {
4036
+ const handleReject = React.useCallback(async () => {
1254
4037
  setIsSubmitting(true);
1255
4038
  setActionType("reject");
1256
4039
  try {
@@ -1259,7 +4042,7 @@ function UserActionModal({
1259
4042
  resetActionState();
1260
4043
  }
1261
4044
  }, [onReject, resetActionState]);
1262
- const handleResend = react.useCallback(async () => {
4045
+ const handleResend = React.useCallback(async () => {
1263
4046
  if (resendCooldownRemaining > 0) return;
1264
4047
  setIsSubmitting(true);
1265
4048
  setActionType("resend");
@@ -1290,32 +4073,32 @@ function UserActionModal({
1290
4073
  children: /* @__PURE__ */ jsxRuntime.jsx(
1291
4074
  reactNative.KeyboardAvoidingView,
1292
4075
  {
1293
- style: [s2.keyboardAvoid, { justifyContent: modalPosition }],
4076
+ style: [s8.keyboardAvoid, { justifyContent: modalPosition }],
1294
4077
  behavior: reactNative.Platform.OS === "ios" ? "padding" : "height",
1295
4078
  keyboardVerticalOffset: reactNative.Platform.OS === "ios" ? 0 : 20,
1296
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s2.modalOverlay, { justifyContent: modalPosition }], children: /* @__PURE__ */ jsxRuntime.jsx(
4079
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [s8.modalOverlay, { justifyContent: modalPosition }], children: /* @__PURE__ */ jsxRuntime.jsx(
1297
4080
  reactNative.ScrollView,
1298
4081
  {
1299
4082
  contentContainerStyle: [
1300
- s2.scrollContent,
4083
+ s8.scrollContent,
1301
4084
  { justifyContent: modalPosition }
1302
4085
  ],
1303
4086
  showsVerticalScrollIndicator: false,
1304
4087
  keyboardShouldPersistTaps: "handled",
1305
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s2.dialog, { width: DIALOG_MAX_WIDTH }], children: [
4088
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [s8.dialog, { width: DIALOG_MAX_WIDTH }], children: [
1306
4089
  /* @__PURE__ */ jsxRuntime.jsx(
1307
4090
  reactNative.Pressable,
1308
4091
  {
1309
4092
  onPress: handleReject,
1310
4093
  disabled: isSubmitting,
1311
- style: s2.closeBtn,
4094
+ style: s8.closeBtn,
1312
4095
  accessibilityLabel: "Close",
1313
4096
  hitSlop: 8,
1314
- children: ({ pressed }) => /* @__PURE__ */ jsxRuntime.jsx(
4097
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1315
4098
  lucideReactNative.X,
1316
4099
  {
1317
4100
  size: 16,
1318
- color: pressed && !isSubmitting ? PAYMAN.foreground : PAYMAN.mutedForeground,
4101
+ color: PAYMAN.mutedForeground,
1319
4102
  strokeWidth: 2.5
1320
4103
  }
1321
4104
  )
@@ -1324,19 +4107,19 @@ function UserActionModal({
1324
4107
  /* @__PURE__ */ jsxRuntime.jsx(
1325
4108
  reactNative.Text,
1326
4109
  {
1327
- style: s2.description,
4110
+ style: s8.description,
1328
4111
  accessibilityLabel: "payman-modal-desc",
1329
4112
  children: userActionRequest.message
1330
4113
  }
1331
4114
  ),
1332
- isPayment ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.transferLabel, children: "Pay" }) : null,
1333
- isPayment && userActionRequest.metadata?.amount ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.heroAmount, children: formatAmountForDisplay(userActionRequest.metadata.amount) }) : null,
1334
- isPayee ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.transferLabel, children: "Create Payee" }) : null,
1335
- isPayee && userActionRequest.metadata?.payeeName ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s2.heroPayeeWrap, children: [
1336
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.heroPayeeName, children: userActionRequest.metadata.payeeName }),
1337
- userActionRequest.metadata.payeeType ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.heroPayeeType, children: userActionRequest.metadata.payeeType }) : null
4115
+ isPayment ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.transferLabel, children: "Pay" }) : null,
4116
+ isPayment && userActionRequest.metadata?.amount ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.heroAmount, children: formatAmountForDisplay(userActionRequest.metadata.amount) }) : null,
4117
+ isPayee ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.transferLabel, children: "Create Payee" }) : null,
4118
+ isPayee && userActionRequest.metadata?.payeeName ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.heroPayeeWrap, children: [
4119
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.heroPayeeName, children: userActionRequest.metadata.payeeName }),
4120
+ userActionRequest.metadata.payeeType ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.heroPayeeType, children: userActionRequest.metadata.payeeType }) : null
1338
4121
  ] }) : null,
1339
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s2.otpWrap, accessibilityLabel: "payman-otp-wrap", children: [
4122
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.otpWrap, accessibilityLabel: "payman-otp-wrap", children: [
1340
4123
  /* @__PURE__ */ jsxRuntime.jsx(
1341
4124
  OtpInput,
1342
4125
  {
@@ -1347,7 +4130,7 @@ function UserActionModal({
1347
4130
  error: otpError
1348
4131
  }
1349
4132
  ),
1350
- isVerifying ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s2.verifyingRow, children: [
4133
+ isVerifying ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.verifyingRow, children: [
1351
4134
  /* @__PURE__ */ jsxRuntime.jsx(
1352
4135
  reactNative.ActivityIndicator,
1353
4136
  {
@@ -1355,10 +4138,10 @@ function UserActionModal({
1355
4138
  color: PAYMAN_BRAND_GREEN
1356
4139
  }
1357
4140
  ),
1358
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.verifyingText, children: MODAL_CONTENT.LOADING_APPROVE })
4141
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.verifyingText, children: MODAL_CONTENT.LOADING_APPROVE })
1359
4142
  ] }) : null
1360
4143
  ] }),
1361
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s2.linksCol, children: [
4144
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.linksCol, children: [
1362
4145
  /* @__PURE__ */ jsxRuntime.jsx(
1363
4146
  reactNative.Pressable,
1364
4147
  {
@@ -1366,15 +4149,14 @@ function UserActionModal({
1366
4149
  disabled: isSubmitting || resendCooldownRemaining > 0,
1367
4150
  accessibilityLabel: "payman-modal-btn-resend",
1368
4151
  testID: "payman-modal-btn-resend",
1369
- children: ({ pressed }) => /* @__PURE__ */ jsxRuntime.jsx(
4152
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1370
4153
  reactNative.Text,
1371
4154
  {
1372
4155
  style: [
1373
- s2.linkText,
1374
- pressed && { opacity: 0.6 },
1375
- (isSubmitting || resendCooldownRemaining > 0) && s2.linkDisabled
4156
+ s8.linkText,
4157
+ (isSubmitting || resendCooldownRemaining > 0) && s8.linkDisabled
1376
4158
  ],
1377
- children: actionType === "resend" ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: MODAL_CONTENT.LOADING_RESEND }) : resendCooldownRemaining > 0 ? `${MODAL_CONTENT.RESEND_AVAILABLE_IN} ${resendCooldownRemaining}s` : BUTTON_LABELS.RESEND_CODE
4159
+ children: actionType === "resend" ? MODAL_CONTENT.LOADING_RESEND : resendCooldownRemaining > 0 ? `${MODAL_CONTENT.RESEND_AVAILABLE_IN} ${resendCooldownRemaining}s` : BUTTON_LABELS.RESEND_CODE
1378
4160
  }
1379
4161
  )
1380
4162
  }
@@ -1386,7 +4168,7 @@ function UserActionModal({
1386
4168
  disabled: isSubmitting,
1387
4169
  accessibilityLabel: "payman-modal-btn-reject",
1388
4170
  testID: "payman-modal-btn-reject",
1389
- children: ({ pressed }) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6 }, children: [
4171
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 6 }, children: [
1390
4172
  isCancelling ? /* @__PURE__ */ jsxRuntime.jsx(
1391
4173
  reactNative.ActivityIndicator,
1392
4174
  {
@@ -1398,11 +4180,8 @@ function UserActionModal({
1398
4180
  reactNative.Text,
1399
4181
  {
1400
4182
  style: [
1401
- s2.linkText,
1402
- pressed && !isSubmitting && {
1403
- color: PAYMAN.foreground
1404
- },
1405
- isSubmitting && !isCancelling && s2.linkDisabled
4183
+ s8.linkText,
4184
+ isSubmitting && !isCancelling && s8.linkDisabled
1406
4185
  ],
1407
4186
  children: isCancelling ? MODAL_CONTENT.LOADING_REJECT : isPayee ? BUTTON_LABELS.CANCEL : BUTTON_LABELS.CANCEL_TRANSFER
1408
4187
  }
@@ -1411,19 +4190,19 @@ function UserActionModal({
1411
4190
  }
1412
4191
  )
1413
4192
  ] }),
1414
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s2.footer, children: [
1415
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.footerPrefix, children: MODAL_CONTENT.SECURED_BY_PREFIX }),
4193
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: s8.footer, children: [
4194
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.footerPrefix, children: MODAL_CONTENT.SECURED_BY_PREFIX }),
1416
4195
  /* @__PURE__ */ jsxRuntime.jsx(
1417
4196
  reactNative.Image,
1418
4197
  {
1419
4198
  source: { uri: payman_mono_crop_blue_default },
1420
- style: s2.footerLogo,
4199
+ style: s8.footerLogo,
1421
4200
  resizeMode: "contain",
1422
4201
  accessibilityElementsHidden: true,
1423
4202
  importantForAccessibility: "no-hide-descendants"
1424
4203
  }
1425
4204
  ),
1426
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s2.footerBrand, children: MODAL_CONTENT.SECURED_BY_BRAND })
4205
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: s8.footerBrand, children: MODAL_CONTENT.SECURED_BY_BRAND })
1427
4206
  ] })
1428
4207
  ] })
1429
4208
  }
@@ -1433,7 +4212,7 @@ function UserActionModal({
1433
4212
  }
1434
4213
  );
1435
4214
  }
1436
- var s2 = reactNative.StyleSheet.create({
4215
+ var s8 = reactNative.StyleSheet.create({
1437
4216
  keyboardAvoid: {
1438
4217
  flex: 1,
1439
4218
  width: "100%"
@@ -1644,7 +4423,7 @@ function FloatingChat({
1644
4423
  defaultOpen = false,
1645
4424
  showNotificationBadge = false
1646
4425
  }) {
1647
- const [isOpen, setIsOpen] = react.useState(defaultOpen);
4426
+ const [isOpen, setIsOpen] = React.useState(defaultOpen);
1648
4427
  const toggleChat = () => setIsOpen(!isOpen);
1649
4428
  const buttonSizes = { sm: "w-12 h-12", md: "w-14 h-14", lg: "w-16 h-16" };
1650
4429
  const iconSizes = { sm: "w-5 h-5", md: "w-6 h-6", lg: "w-7 h-7" };
@@ -1745,9 +4524,9 @@ function FloatingChat({
1745
4524
  )
1746
4525
  ] });
1747
4526
  }
1748
- var PaymanChatContext = react.createContext(void 0);
4527
+ var PaymanChatContext = React.createContext(void 0);
1749
4528
  function usePaymanChat() {
1750
- const ctx = react.useContext(PaymanChatContext);
4529
+ const ctx = React.useContext(PaymanChatContext);
1751
4530
  if (!ctx) {
1752
4531
  throw new Error("usePaymanChat must be used within a PaymanChat component");
1753
4532
  }