@mobileai/react-native 0.9.18 → 0.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +28 -20
  2. package/MobileAIFloatingOverlay.podspec +25 -0
  3. package/android/build.gradle +61 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +151 -0
  6. package/android/src/main/java/com/mobileai/overlay/MobileAIOverlayPackage.kt +23 -0
  7. package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +45 -0
  8. package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +29 -0
  9. package/ios/MobileAIFloatingOverlayComponentView.mm +73 -0
  10. package/lib/module/components/AIAgent.js +902 -136
  11. package/lib/module/components/AIConsentDialog.js +439 -0
  12. package/lib/module/components/AgentChatBar.js +828 -134
  13. package/lib/module/components/AgentOverlay.js +2 -1
  14. package/lib/module/components/DiscoveryTooltip.js +21 -9
  15. package/lib/module/components/FloatingOverlayWrapper.js +108 -0
  16. package/lib/module/components/Icons.js +123 -0
  17. package/lib/module/config/endpoints.js +12 -2
  18. package/lib/module/core/AgentRuntime.js +373 -27
  19. package/lib/module/core/FiberAdapter.js +56 -0
  20. package/lib/module/core/FiberTreeWalker.js +186 -80
  21. package/lib/module/core/IdleDetector.js +19 -0
  22. package/lib/module/core/NativeAlertInterceptor.js +191 -0
  23. package/lib/module/core/systemPrompt.js +203 -45
  24. package/lib/module/index.js +3 -0
  25. package/lib/module/providers/GeminiProvider.js +72 -56
  26. package/lib/module/providers/ProviderFactory.js +6 -2
  27. package/lib/module/services/AudioInputService.js +3 -12
  28. package/lib/module/services/AudioOutputService.js +1 -13
  29. package/lib/module/services/ConversationService.js +166 -0
  30. package/lib/module/services/MobileAIKnowledgeRetriever.js +41 -0
  31. package/lib/module/services/VoiceService.js +29 -8
  32. package/lib/module/services/telemetry/MobileAI.js +44 -0
  33. package/lib/module/services/telemetry/TelemetryService.js +13 -1
  34. package/lib/module/services/telemetry/TouchAutoCapture.js +44 -18
  35. package/lib/module/specs/FloatingOverlayNativeComponent.ts +19 -0
  36. package/lib/module/support/CSATSurvey.js +95 -12
  37. package/lib/module/support/EscalationSocket.js +70 -1
  38. package/lib/module/support/ReportedIssueEventSource.js +148 -0
  39. package/lib/module/support/escalateTool.js +4 -2
  40. package/lib/module/support/index.js +1 -0
  41. package/lib/module/support/reportIssueTool.js +127 -0
  42. package/lib/module/support/supportPrompt.js +77 -9
  43. package/lib/module/tools/guideTool.js +2 -1
  44. package/lib/module/tools/longPressTool.js +4 -3
  45. package/lib/module/tools/pickerTool.js +6 -4
  46. package/lib/module/tools/tapTool.js +12 -3
  47. package/lib/module/tools/typeTool.js +19 -10
  48. package/lib/module/utils/logger.js +175 -6
  49. package/lib/typescript/react-native.config.d.ts +11 -0
  50. package/lib/typescript/src/components/AIAgent.d.ts +28 -2
  51. package/lib/typescript/src/components/AIConsentDialog.d.ts +153 -0
  52. package/lib/typescript/src/components/AgentChatBar.d.ts +15 -2
  53. package/lib/typescript/src/components/DiscoveryTooltip.d.ts +3 -1
  54. package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +51 -0
  55. package/lib/typescript/src/components/Icons.d.ts +8 -0
  56. package/lib/typescript/src/config/endpoints.d.ts +5 -3
  57. package/lib/typescript/src/core/AgentRuntime.d.ts +4 -0
  58. package/lib/typescript/src/core/FiberAdapter.d.ts +25 -0
  59. package/lib/typescript/src/core/FiberTreeWalker.d.ts +2 -0
  60. package/lib/typescript/src/core/IdleDetector.d.ts +11 -0
  61. package/lib/typescript/src/core/NativeAlertInterceptor.d.ts +55 -0
  62. package/lib/typescript/src/core/types.d.ts +106 -1
  63. package/lib/typescript/src/index.d.ts +9 -4
  64. package/lib/typescript/src/providers/GeminiProvider.d.ts +6 -5
  65. package/lib/typescript/src/services/ConversationService.d.ts +55 -0
  66. package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +9 -0
  67. package/lib/typescript/src/services/telemetry/MobileAI.d.ts +7 -0
  68. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +1 -1
  69. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +9 -6
  70. package/lib/typescript/src/services/telemetry/types.d.ts +3 -1
  71. package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +17 -0
  72. package/lib/typescript/src/support/EscalationSocket.d.ts +17 -0
  73. package/lib/typescript/src/support/ReportedIssueEventSource.d.ts +24 -0
  74. package/lib/typescript/src/support/escalateTool.d.ts +5 -0
  75. package/lib/typescript/src/support/index.d.ts +2 -1
  76. package/lib/typescript/src/support/reportIssueTool.d.ts +20 -0
  77. package/lib/typescript/src/support/types.d.ts +56 -1
  78. package/lib/typescript/src/utils/logger.d.ts +15 -0
  79. package/package.json +20 -5
  80. package/react-native.config.js +12 -0
  81. package/src/specs/FloatingOverlayNativeComponent.ts +19 -0
@@ -8,12 +8,12 @@
8
8
 
9
9
  import { useState, useRef, useEffect } from 'react';
10
10
  import { View, TextInput, Pressable, Text, StyleSheet, Animated, PanResponder, ScrollView, Keyboard, Platform, useWindowDimensions } from 'react-native';
11
- import { MicIcon, SpeakerIcon, SendArrowIcon, StopIcon, LoadingDots, AIBadge, CloseIcon } from "./Icons.js";
11
+ import { MicIcon, SpeakerIcon, SendArrowIcon, StopIcon, LoadingDots, AIBadge, HistoryIcon, NewChatIcon, CloseIcon } from "./Icons.js";
12
12
  import { logger } from "../utils/logger.js";
13
13
  import { DiscoveryTooltip } from "./DiscoveryTooltip.js";
14
14
 
15
15
  // ─── Props ─────────────────────────────────────────────────────
16
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
17
  // ─── Mode Selector ─────────────────────────────────────────────
18
18
 
19
19
  function ModeSelector({
@@ -174,9 +174,18 @@ function TextInputRow({
174
174
  isArabic,
175
175
  theme
176
176
  }) {
177
+ const inputRef = useRef(null);
178
+ const handleSendWithClear = () => {
179
+ onSend();
180
+ // Imperatively clear the native TextInput — controlled `value=''` can be
181
+ // ignored by the iOS native layer when editable flips to false in the same
182
+ // render batch.
183
+ inputRef.current?.clear();
184
+ };
177
185
  return /*#__PURE__*/_jsxs(View, {
178
186
  style: styles.inputRow,
179
187
  children: [/*#__PURE__*/_jsx(TextInput, {
188
+ ref: inputRef,
180
189
  style: [styles.input, isArabic && styles.inputRTL, theme?.inputBackgroundColor ? {
181
190
  backgroundColor: theme.inputBackgroundColor
182
191
  } : undefined, theme?.textColor ? {
@@ -186,10 +195,11 @@ function TextInputRow({
186
195
  placeholderTextColor: theme?.textColor ? `${theme.textColor}66` : '#999',
187
196
  value: text,
188
197
  onChangeText: setText,
189
- onSubmitEditing: onSend,
190
- returnKeyType: "send",
198
+ onSubmitEditing: handleSendWithClear,
199
+ returnKeyType: "default",
200
+ blurOnSubmit: false,
191
201
  editable: !isThinking,
192
- multiline: false
202
+ multiline: true
193
203
  }), /*#__PURE__*/_jsx(DictationButton, {
194
204
  language: isArabic ? 'ar' : 'en',
195
205
  onTranscript: t => setText(t),
@@ -198,7 +208,7 @@ function TextInputRow({
198
208
  style: [styles.sendButton, isThinking && styles.sendButtonDisabled, theme?.primaryColor ? {
199
209
  backgroundColor: theme.primaryColor
200
210
  } : undefined],
201
- onPress: onSend,
211
+ onPress: handleSendWithClear,
202
212
  disabled: isThinking || !text.trim(),
203
213
  accessibilityLabel: "Send request to AI Agent",
204
214
  children: isThinking ? /*#__PURE__*/_jsx(LoadingDots, {
@@ -279,9 +289,9 @@ function VoiceControlsRow({
279
289
  export function AgentChatBar({
280
290
  onSend,
281
291
  isThinking,
292
+ statusText,
282
293
  lastResult,
283
294
  language,
284
- onDismiss,
285
295
  availableModes = ['text'],
286
296
  mode = 'text',
287
297
  onModeChange,
@@ -297,43 +307,104 @@ export function AgentChatBar({
297
307
  selectedTicketId,
298
308
  onTicketSelect,
299
309
  autoExpandTrigger = 0,
300
- lastUserMessage,
301
310
  unreadCounts = {},
302
311
  totalUnread = 0,
303
312
  showDiscoveryTooltip = false,
304
- onTooltipDismiss
313
+ discoveryTooltipMessage,
314
+ onTooltipDismiss,
315
+ chatMessages = [],
316
+ conversations = [],
317
+ isLoadingHistory = false,
318
+ onConversationSelect,
319
+ onNewConversation,
320
+ pendingApprovalQuestion,
321
+ onPendingApprovalAction
305
322
  }) {
306
323
  const [text, setText] = useState('');
307
324
  const [isExpanded, setIsExpanded] = useState(false);
325
+ const [localUnread, setLocalUnread] = useState(0);
326
+ const [fabX, setFabX] = useState(10);
327
+ const [showHistory, setShowHistory] = useState(false);
328
+ const prevMsgCount = useRef(chatMessages.length);
329
+ const scrollRef = useRef(null);
308
330
  const {
309
- height
331
+ height,
332
+ width
310
333
  } = useWindowDimensions();
311
334
  const isArabic = language === 'ar';
335
+ const [panelHeight, setPanelHeight] = useState(0);
336
+ const preKeyboardYRef = useRef(null);
337
+ const previousThinkingRef = useRef(false);
338
+ const autoCollapsedForThinkingRef = useRef(false);
339
+
340
+ // Track incoming AI messages while collapsed
341
+ useEffect(() => {
342
+ if (chatMessages.length > prevMsgCount.current && !isExpanded) {
343
+ setLocalUnread(prev => prev + (chatMessages.length - prevMsgCount.current));
344
+ }
345
+ prevMsgCount.current = chatMessages.length;
346
+ }, [chatMessages.length, isExpanded]);
347
+ const displayUnread = totalUnread + localUnread;
312
348
 
313
349
  // Auto-expand when triggered (e.g. on escalation)
314
350
  useEffect(() => {
315
351
  if (autoExpandTrigger > 0) setIsExpanded(true);
316
352
  }, [autoExpandTrigger]);
353
+ useEffect(() => {
354
+ const wasThinking = previousThinkingRef.current;
355
+ if (pendingApprovalQuestion) {
356
+ setIsExpanded(true);
357
+ autoCollapsedForThinkingRef.current = false;
358
+ }
359
+ if (wasThinking && !isThinking) {
360
+ autoCollapsedForThinkingRef.current = false;
361
+ }
362
+ previousThinkingRef.current = isThinking;
363
+ }, [isThinking, isExpanded, mode, pendingApprovalQuestion]);
317
364
  const pan = useRef(new Animated.ValueXY({
318
365
  x: 10,
319
366
  y: height - 200
320
367
  })).current;
321
- const keyboardOffset = useRef(new Animated.Value(0)).current;
368
+ const tooltipSide = fabX < width / 2 ? 'right' : 'left';
369
+ useEffect(() => {
370
+ const listenerId = pan.x.addListener(({
371
+ value
372
+ }) => {
373
+ setFabX(value);
374
+ });
375
+ return () => {
376
+ pan.x.removeListener(listenerId);
377
+ };
378
+ }, [pan.x]);
322
379
 
323
380
  // ─── Keyboard Handling ──────────────────────────────────────
324
381
  useEffect(() => {
325
382
  const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
326
383
  const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
384
+ const keyboardMargin = 12;
327
385
  const showSub = Keyboard.addListener(showEvent, e => {
328
- Animated.timing(keyboardOffset, {
329
- toValue: -e.endCoordinates.height,
330
- duration: Platform.OS === 'ios' ? e.duration || 250 : 200,
331
- useNativeDriver: false
332
- }).start();
386
+ if (!isExpanded || mode !== 'text' || panelHeight <= 0) return;
387
+ pan.y.stopAnimation(currentY => {
388
+ const targetY = Math.max(keyboardMargin, height - e.endCoordinates.height - panelHeight - keyboardMargin);
389
+
390
+ // Preserve the pre-keyboard position so we can restore it on hide.
391
+ preKeyboardYRef.current = currentY;
392
+
393
+ // Only lift the widget if the keyboard would overlap it.
394
+ if (currentY <= targetY) return;
395
+ Animated.timing(pan.y, {
396
+ toValue: targetY,
397
+ duration: Platform.OS === 'ios' ? e.duration || 250 : 200,
398
+ useNativeDriver: false
399
+ }).start();
400
+ });
333
401
  });
334
402
  const hideSub = Keyboard.addListener(hideEvent, () => {
335
- Animated.timing(keyboardOffset, {
336
- toValue: 0,
403
+ const restoreY = preKeyboardYRef.current;
404
+ if (restoreY == null) return;
405
+ preKeyboardYRef.current = null;
406
+ Animated.timing(pan.y, {
407
+ toValue: restoreY,
337
408
  duration: 200,
338
409
  useNativeDriver: false
339
410
  }).start();
@@ -342,7 +413,7 @@ export function AgentChatBar({
342
413
  showSub.remove();
343
414
  hideSub.remove();
344
415
  };
345
- }, [keyboardOffset]);
416
+ }, [height, isExpanded, mode, pan.y, panelHeight]);
346
417
  const panResponder = useRef(PanResponder.create({
347
418
  onMoveShouldSetPanResponder: (_, gestureState) => {
348
419
  return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
@@ -389,9 +460,11 @@ export function AgentChatBar({
389
460
  } : undefined],
390
461
  onPress: () => {
391
462
  onTooltipDismiss?.();
463
+ setLocalUnread(0);
464
+ autoCollapsedForThinkingRef.current = false;
392
465
  setIsExpanded(true);
393
466
  },
394
- accessibilityLabel: totalUnread > 0 ? `Open AI Agent Chat - ${totalUnread} unread messages` : 'Open AI Agent Chat',
467
+ accessibilityLabel: displayUnread > 0 ? `Open AI Agent Chat - ${displayUnread} unread messages` : 'Open AI Agent Chat',
395
468
  children: isThinking ? /*#__PURE__*/_jsx(LoadingDots, {
396
469
  size: 28,
397
470
  color: theme?.textColor || '#fff'
@@ -401,13 +474,57 @@ export function AgentChatBar({
401
474
  }), showDiscoveryTooltip && /*#__PURE__*/_jsx(DiscoveryTooltip, {
402
475
  language: language,
403
476
  primaryColor: theme?.primaryColor,
477
+ message: discoveryTooltipMessage,
478
+ side: tooltipSide,
404
479
  onDismiss: () => onTooltipDismiss?.()
405
- }), totalUnread > 0 && /*#__PURE__*/_jsx(View, {
480
+ }), localUnread > 0 && chatMessages.length > 0 && /*#__PURE__*/_jsxs(Pressable, {
481
+ style: [styles.unreadPopup, isArabic ? styles.unreadPopupRTL : styles.unreadPopupLTR],
482
+ onPress: () => {
483
+ onTooltipDismiss?.();
484
+ setLocalUnread(0);
485
+ setIsExpanded(true);
486
+ },
487
+ children: [/*#__PURE__*/_jsx(Text, {
488
+ style: [styles.unreadPopupText, {
489
+ textAlign: isArabic ? 'right' : 'left'
490
+ }],
491
+ numberOfLines: 2,
492
+ children: (() => {
493
+ const lastMsg = [...chatMessages].reverse().find(m => m.role === 'assistant');
494
+ if (!lastMsg) return isArabic ? 'رسالة جديدة' : 'New message';
495
+ const content = Array.isArray(lastMsg.content) ? lastMsg.content.map(c => c.type === 'text' ? c.text : '').join('') : lastMsg.content;
496
+ return content || (isArabic ? 'رسالة جديدة' : 'New message');
497
+ })()
498
+ }), displayUnread > 1 && /*#__PURE__*/_jsx(View, {
499
+ style: [styles.fabUnreadBadge, styles.popupBadgeOverride],
500
+ pointerEvents: "none",
501
+ children: /*#__PURE__*/_jsx(Text, {
502
+ style: styles.fabUnreadBadgeText,
503
+ children: displayUnread > 99 ? '99+' : displayUnread
504
+ })
505
+ })]
506
+ }), isThinking && !pendingApprovalQuestion && /*#__PURE__*/_jsxs(Pressable, {
507
+ style: [styles.statusPopup, isArabic ? styles.unreadPopupRTL : styles.unreadPopupLTR],
508
+ onPress: () => {
509
+ autoCollapsedForThinkingRef.current = false;
510
+ setIsExpanded(true);
511
+ },
512
+ children: [/*#__PURE__*/_jsx(LoadingDots, {
513
+ size: 14,
514
+ color: "#111827"
515
+ }), /*#__PURE__*/_jsx(Text, {
516
+ style: [styles.statusPopupText, {
517
+ textAlign: isArabic ? 'right' : 'left'
518
+ }],
519
+ numberOfLines: 2,
520
+ children: statusText || (isArabic ? 'جاري التنفيذ...' : 'Working...')
521
+ })]
522
+ }), displayUnread > 0 && localUnread === 0 && /*#__PURE__*/_jsx(View, {
406
523
  style: styles.fabUnreadBadge,
407
524
  pointerEvents: "none",
408
525
  children: /*#__PURE__*/_jsx(Text, {
409
526
  style: styles.fabUnreadBadgeText,
410
- children: totalUnread > 99 ? '99+' : totalUnread
527
+ children: displayUnread > 99 ? '99+' : displayUnread
411
528
  })
412
529
  })]
413
530
  });
@@ -417,111 +534,330 @@ export function AgentChatBar({
417
534
 
418
535
  return /*#__PURE__*/_jsxs(Animated.View, {
419
536
  style: [styles.expandedContainer, pan.getLayout(), {
420
- transform: [{
421
- translateY: keyboardOffset
422
- }]
537
+ maxHeight: height * 0.65
423
538
  }, theme?.backgroundColor ? {
424
539
  backgroundColor: theme.backgroundColor
425
540
  } : undefined],
426
- children: [/*#__PURE__*/_jsxs(View, {
541
+ onLayout: event => {
542
+ const nextHeight = event.nativeEvent.layout.height;
543
+ if (Math.abs(nextHeight - panelHeight) > 1) {
544
+ setPanelHeight(nextHeight);
545
+ }
546
+ },
547
+ children: [/*#__PURE__*/_jsx(View, {
427
548
  ...panResponder.panHandlers,
428
549
  style: styles.dragHandleArea,
429
550
  accessibilityLabel: "Drag AI Agent",
430
- children: [/*#__PURE__*/_jsx(View, {
551
+ children: /*#__PURE__*/_jsx(View, {
431
552
  style: styles.dragGrip
432
- }), /*#__PURE__*/_jsx(Pressable, {
433
- onPress: () => setIsExpanded(false),
434
- style: styles.minimizeBtn,
435
- accessibilityLabel: "Minimize AI Agent",
553
+ })
554
+ }), /*#__PURE__*/_jsx(Pressable, {
555
+ onPress: () => {
556
+ setIsExpanded(false);
557
+ setShowHistory(false);
558
+ },
559
+ style: styles.minimizeBtn,
560
+ accessibilityLabel: "Minimize AI Agent",
561
+ children: /*#__PURE__*/_jsx(Text, {
562
+ style: styles.minimizeText,
563
+ children: "\u2014"
564
+ })
565
+ }), onConversationSelect && !showHistory && /*#__PURE__*/_jsxs(View, {
566
+ style: historyStyles.headerActions,
567
+ children: [/*#__PURE__*/_jsxs(Pressable, {
568
+ style: historyStyles.historyBtn,
569
+ onPress: () => setShowHistory(true),
570
+ accessibilityLabel: "View conversation history",
571
+ hitSlop: 8,
572
+ children: [/*#__PURE__*/_jsx(HistoryIcon, {
573
+ size: 18,
574
+ color: "rgba(255,255,255,0.55)"
575
+ }), conversations.length > 0 && /*#__PURE__*/_jsx(View, {
576
+ style: historyStyles.historyCountBadge,
577
+ children: /*#__PURE__*/_jsx(Text, {
578
+ style: historyStyles.historyCountBadgeText,
579
+ children: conversations.length > 9 ? '9+' : conversations.length
580
+ })
581
+ })]
582
+ }), onNewConversation && /*#__PURE__*/_jsx(Pressable, {
583
+ style: historyStyles.quickNewBtn,
584
+ onPress: onNewConversation,
585
+ accessibilityLabel: "Start new conversation",
586
+ hitSlop: 8,
436
587
  children: /*#__PURE__*/_jsx(Text, {
437
- style: styles.minimizeText,
438
- children: "\u2014"
588
+ style: historyStyles.quickNewBtnText,
589
+ children: "+"
439
590
  })
440
591
  })]
441
- }), /*#__PURE__*/_jsx(ModeSelector, {
592
+ }), /*#__PURE__*/_jsx(View, {
593
+ ...panResponder.panHandlers,
594
+ style: [styles.cornerHandle, styles.cornerTL],
595
+ pointerEvents: "box-only",
596
+ children: /*#__PURE__*/_jsx(View, {
597
+ style: [styles.cornerIndicator, styles.cornerIndicatorTL]
598
+ })
599
+ }), /*#__PURE__*/_jsx(View, {
600
+ ...panResponder.panHandlers,
601
+ style: [styles.cornerHandle, styles.cornerTR],
602
+ pointerEvents: "box-only",
603
+ children: /*#__PURE__*/_jsx(View, {
604
+ style: [styles.cornerIndicator, styles.cornerIndicatorTR]
605
+ })
606
+ }), /*#__PURE__*/_jsx(View, {
607
+ ...panResponder.panHandlers,
608
+ style: [styles.cornerHandle, styles.cornerBL],
609
+ pointerEvents: "box-only",
610
+ children: /*#__PURE__*/_jsx(View, {
611
+ style: [styles.cornerIndicator, styles.cornerIndicatorBL]
612
+ })
613
+ }), /*#__PURE__*/_jsx(View, {
614
+ ...panResponder.panHandlers,
615
+ style: [styles.cornerHandle, styles.cornerBR],
616
+ pointerEvents: "box-only",
617
+ children: /*#__PURE__*/_jsx(View, {
618
+ style: [styles.cornerIndicator, styles.cornerIndicatorBR]
619
+ })
620
+ }), !showHistory && /*#__PURE__*/_jsx(ModeSelector, {
442
621
  modes: availableModes,
443
622
  activeMode: mode,
444
623
  onSelect: m => onModeChange?.(m),
445
624
  isArabic: isArabic,
446
625
  totalUnread: totalUnread
447
- }), lastResult && mode !== 'human' && (() => {
448
- const cleanMessage = lastResult.message.trim();
449
- return /*#__PURE__*/_jsxs(View, {
450
- style: [styles.resultBubble, lastResult.success ? [styles.resultSuccess, theme?.successColor ? {
451
- backgroundColor: theme.successColor
452
- } : undefined] : [styles.resultError, theme?.errorColor ? {
453
- backgroundColor: theme.errorColor
454
- } : undefined]],
455
- children: [/*#__PURE__*/_jsx(ScrollView, {
456
- style: styles.resultScroll,
457
- nestedScrollEnabled: true,
458
- children: /*#__PURE__*/_jsx(Text, {
459
- style: [styles.resultText, {
460
- textAlign: isArabic ? 'right' : 'left'
461
- }, theme?.textColor ? {
462
- color: theme.textColor
463
- } : undefined],
464
- children: lastUserMessage ?? cleanMessage
626
+ }), showHistory && /*#__PURE__*/_jsxs(View, {
627
+ style: historyStyles.panel,
628
+ children: [/*#__PURE__*/_jsxs(View, {
629
+ style: historyStyles.headerRow,
630
+ children: [/*#__PURE__*/_jsx(Pressable, {
631
+ style: historyStyles.backBtn,
632
+ onPress: () => setShowHistory(false),
633
+ accessibilityLabel: "Back to chat",
634
+ hitSlop: 8,
635
+ children: /*#__PURE__*/_jsxs(View, {
636
+ style: {
637
+ flexDirection: 'row',
638
+ alignItems: 'center',
639
+ gap: 4
640
+ },
641
+ children: [/*#__PURE__*/_jsx(CloseIcon, {
642
+ size: 13,
643
+ color: "#7B68EE"
644
+ }), /*#__PURE__*/_jsx(Text, {
645
+ style: historyStyles.backBtnText,
646
+ children: "Back"
647
+ })]
465
648
  })
466
- }), onDismiss && /*#__PURE__*/_jsx(Pressable, {
467
- style: styles.dismissButton,
468
- onPress: onDismiss,
469
- hitSlop: 12,
470
- children: /*#__PURE__*/_jsx(CloseIcon, {
471
- size: 14,
472
- color: theme?.textColor ? theme.textColor : 'rgba(255, 255, 255, 0.6)'
649
+ }), /*#__PURE__*/_jsxs(View, {
650
+ style: {
651
+ flexDirection: 'row',
652
+ alignItems: 'center',
653
+ gap: 6
654
+ },
655
+ children: [/*#__PURE__*/_jsx(HistoryIcon, {
656
+ size: 15,
657
+ color: "rgba(255,255,255,0.7)"
658
+ }), /*#__PURE__*/_jsx(Text, {
659
+ style: historyStyles.headerTitle,
660
+ children: "History"
661
+ })]
662
+ }), /*#__PURE__*/_jsx(Pressable, {
663
+ style: historyStyles.newBtn,
664
+ onPress: () => {
665
+ onNewConversation?.();
666
+ setShowHistory(false);
667
+ },
668
+ accessibilityLabel: "Start new conversation",
669
+ hitSlop: 8,
670
+ children: /*#__PURE__*/_jsxs(View, {
671
+ style: {
672
+ flexDirection: 'row',
673
+ alignItems: 'center',
674
+ gap: 5
675
+ },
676
+ children: [/*#__PURE__*/_jsx(NewChatIcon, {
677
+ size: 14,
678
+ color: "#7B68EE"
679
+ }), /*#__PURE__*/_jsx(Text, {
680
+ style: historyStyles.newBtnText,
681
+ children: "New"
682
+ })]
473
683
  })
474
684
  })]
475
- });
476
- })(), mode === 'text' && /*#__PURE__*/_jsx(TextInputRow, {
477
- text: text,
478
- setText: setText,
479
- onSend: handleSend,
480
- isThinking: isThinking,
481
- isArabic: isArabic,
482
- theme: theme
483
- }), mode === 'human' && !selectedTicketId && /*#__PURE__*/_jsx(ScrollView, {
484
- style: styles.ticketList,
485
- nestedScrollEnabled: true,
486
- children: tickets.length === 0 ? /*#__PURE__*/_jsx(Text, {
487
- style: styles.emptyText,
488
- children: "No active tickets"
489
- }) : tickets.map(ticket => {
490
- const unreadCount = unreadCounts[ticket.id] || 0;
491
- return /*#__PURE__*/_jsxs(Pressable, {
492
- style: styles.ticketCard,
493
- onPress: () => onTicketSelect?.(ticket.id),
494
- children: [/*#__PURE__*/_jsxs(View, {
495
- style: styles.ticketTopRow,
496
- children: [/*#__PURE__*/_jsx(Text, {
497
- style: styles.ticketReason,
498
- numberOfLines: 2,
499
- children: ticket.history.length > 0 ? ticket.history[ticket.history.length - 1]?.content ?? ticket.reason : ticket.reason
500
- }), unreadCount > 0 && /*#__PURE__*/_jsx(View, {
501
- style: styles.unreadBadge,
502
- children: /*#__PURE__*/_jsx(Text, {
503
- style: styles.unreadBadgeText,
504
- children: unreadCount > 99 ? '99+' : unreadCount
505
- })
506
- })]
685
+ }), isLoadingHistory && conversations.length === 0 && /*#__PURE__*/_jsx(View, {
686
+ style: historyStyles.shimmerWrap,
687
+ children: [1, 2, 3].map(i => /*#__PURE__*/_jsxs(View, {
688
+ style: historyStyles.shimmerCard,
689
+ children: [/*#__PURE__*/_jsx(View, {
690
+ style: [historyStyles.shimmerLine, {
691
+ width: '70%'
692
+ }]
507
693
  }), /*#__PURE__*/_jsx(View, {
508
- style: styles.ticketMeta,
694
+ style: [historyStyles.shimmerLine, {
695
+ width: '45%',
696
+ marginTop: 6,
697
+ opacity: 0.5
698
+ }]
699
+ })]
700
+ }, i))
701
+ }), !isLoadingHistory && conversations.length === 0 && /*#__PURE__*/_jsxs(View, {
702
+ style: historyStyles.emptyWrap,
703
+ children: [/*#__PURE__*/_jsx(HistoryIcon, {
704
+ size: 36,
705
+ color: "rgba(255,255,255,0.25)"
706
+ }), /*#__PURE__*/_jsx(Text, {
707
+ style: historyStyles.emptyTitle,
708
+ children: "No previous conversations"
709
+ }), /*#__PURE__*/_jsx(Text, {
710
+ style: historyStyles.emptySubtitle,
711
+ children: "Your AI conversations will appear here"
712
+ })]
713
+ }), conversations.length > 0 && /*#__PURE__*/_jsx(ScrollView, {
714
+ style: {
715
+ maxHeight: height * 0.65 - 130
716
+ },
717
+ nestedScrollEnabled: true,
718
+ showsVerticalScrollIndicator: false,
719
+ children: conversations.map(conv => {
720
+ const relativeDate = getRelativeDate(conv.updatedAt);
721
+ return /*#__PURE__*/_jsxs(Pressable, {
722
+ style: ({
723
+ pressed
724
+ }) => [historyStyles.convCard, pressed && historyStyles.convCardPressed],
725
+ onPress: () => {
726
+ onConversationSelect?.(conv.id);
727
+ setShowHistory(false);
728
+ },
729
+ accessibilityLabel: `Load conversation: ${conv.title}`,
730
+ children: [/*#__PURE__*/_jsxs(View, {
731
+ style: historyStyles.convCardTop,
732
+ children: [/*#__PURE__*/_jsx(Text, {
733
+ style: historyStyles.convTitle,
734
+ numberOfLines: 1,
735
+ children: conv.title
736
+ }), /*#__PURE__*/_jsx(View, {
737
+ style: historyStyles.convMsgBadge,
738
+ children: /*#__PURE__*/_jsx(Text, {
739
+ style: historyStyles.convMsgBadgeText,
740
+ children: conv.messageCount
741
+ })
742
+ })]
743
+ }), /*#__PURE__*/_jsx(Text, {
744
+ style: historyStyles.convPreview,
745
+ numberOfLines: 1,
746
+ children: conv.preview || 'No messages'
747
+ }), /*#__PURE__*/_jsx(Text, {
748
+ style: historyStyles.convDate,
749
+ children: relativeDate
750
+ })]
751
+ }, conv.id);
752
+ })
753
+ })]
754
+ }), !showHistory && /*#__PURE__*/_jsxs(_Fragment, {
755
+ children: [mode !== 'human' && chatMessages.length > 0 && /*#__PURE__*/_jsxs(ScrollView, {
756
+ style: [styles.messageList, {
757
+ maxHeight: height * 0.65 - 178
758
+ }],
759
+ nestedScrollEnabled: true,
760
+ ref: scrollRef,
761
+ onContentSizeChange: () => scrollRef.current?.scrollToEnd({
762
+ animated: true
763
+ }),
764
+ children: [chatMessages.filter(msg => msg.role === 'user' || msg.role === 'assistant').map(msg => {
765
+ const isUser = msg.role === 'user';
766
+ const contentText = Array.isArray(msg.content) ? msg.content.map(c => c.type === 'text' ? c.text : '').join('') : msg.content;
767
+ if (!contentText || contentText.trim() === '') return null;
768
+ return /*#__PURE__*/_jsx(View, {
769
+ style: [styles.messageBubble, isUser ? styles.messageBubbleUser : styles.messageBubbleAI, isUser && theme?.primaryColor ? {
770
+ backgroundColor: theme.primaryColor
771
+ } : undefined],
509
772
  children: /*#__PURE__*/_jsx(Text, {
510
- style: [styles.ticketStatus, ticket.status === 'open' && styles.statusOpen],
511
- children: ticket.status
773
+ style: [styles.messageText, isUser ? styles.messageTextUser : styles.messageTextAI, {
774
+ textAlign: isArabic ? 'right' : 'left'
775
+ }],
776
+ children: contentText
512
777
  })
778
+ }, msg.id || `${msg.role}-${Math.random()}`);
779
+ }), isThinking && /*#__PURE__*/_jsx(View, {
780
+ style: [styles.messageBubble, styles.messageBubbleAI],
781
+ children: /*#__PURE__*/_jsx(LoadingDots, {
782
+ size: 18,
783
+ color: "#fff"
784
+ })
785
+ })]
786
+ }), mode === 'text' && /*#__PURE__*/_jsxs(_Fragment, {
787
+ children: [pendingApprovalQuestion && onPendingApprovalAction && /*#__PURE__*/_jsxs(View, {
788
+ style: styles.approvalPanel,
789
+ children: [/*#__PURE__*/_jsx(Text, {
790
+ style: styles.approvalHint,
791
+ children: "The AI agent is requesting permission to perform this action. Tap \"Do it\" to approve, or \"Don\u2019t do it\" to cancel."
792
+ }), /*#__PURE__*/_jsxs(View, {
793
+ style: styles.approvalActions,
794
+ children: [/*#__PURE__*/_jsx(Pressable, {
795
+ style: [styles.approvalActionBtn, styles.approvalActionSecondary],
796
+ onPress: () => onPendingApprovalAction('reject'),
797
+ children: /*#__PURE__*/_jsx(Text, {
798
+ style: [styles.approvalActionText, styles.approvalActionSecondaryText],
799
+ children: "Don\u2019t do it"
800
+ })
801
+ }), /*#__PURE__*/_jsx(Pressable, {
802
+ style: [styles.approvalActionBtn, styles.approvalActionPrimary],
803
+ onPress: () => onPendingApprovalAction('approve'),
804
+ children: /*#__PURE__*/_jsx(Text, {
805
+ style: [styles.approvalActionText, styles.approvalActionPrimaryText],
806
+ children: "Do it"
807
+ })
808
+ })]
513
809
  })]
514
- }, ticket.id);
515
- })
516
- }), mode === 'human' && selectedTicketId && null, mode === 'voice' && /*#__PURE__*/_jsx(VoiceControlsRow, {
517
- isMicActive: isMicActive,
518
- isSpeakerMuted: isSpeakerMuted,
519
- onMicToggle: onMicToggle || (() => {}),
520
- onSpeakerToggle: onSpeakerToggle || (() => {}),
521
- isAISpeaking: isAISpeaking,
522
- isVoiceConnected: isVoiceConnected,
523
- isArabic: isArabic,
524
- onStopSession: onStopSession
810
+ }), /*#__PURE__*/_jsx(TextInputRow, {
811
+ text: text,
812
+ setText: setText,
813
+ onSend: handleSend,
814
+ isThinking: isThinking,
815
+ isArabic: isArabic,
816
+ theme: theme
817
+ })]
818
+ }), mode === 'human' && !selectedTicketId && /*#__PURE__*/_jsx(ScrollView, {
819
+ style: styles.ticketList,
820
+ nestedScrollEnabled: true,
821
+ children: tickets.length === 0 ? /*#__PURE__*/_jsx(Text, {
822
+ style: styles.emptyText,
823
+ children: "No active tickets"
824
+ }) : tickets.map(ticket => {
825
+ const unreadCount = unreadCounts[ticket.id] || 0;
826
+ return /*#__PURE__*/_jsxs(Pressable, {
827
+ style: styles.ticketCard,
828
+ onPress: () => onTicketSelect?.(ticket.id),
829
+ children: [/*#__PURE__*/_jsxs(View, {
830
+ style: styles.ticketTopRow,
831
+ children: [/*#__PURE__*/_jsx(Text, {
832
+ style: styles.ticketReason,
833
+ numberOfLines: 2,
834
+ children: ticket.history.length > 0 ? ticket.history[ticket.history.length - 1]?.content ?? ticket.reason : ticket.reason
835
+ }), unreadCount > 0 && /*#__PURE__*/_jsx(View, {
836
+ style: styles.unreadBadge,
837
+ children: /*#__PURE__*/_jsx(Text, {
838
+ style: styles.unreadBadgeText,
839
+ children: unreadCount > 99 ? '99+' : unreadCount
840
+ })
841
+ })]
842
+ }), /*#__PURE__*/_jsx(View, {
843
+ style: styles.ticketMeta,
844
+ children: /*#__PURE__*/_jsx(Text, {
845
+ style: [styles.ticketStatus, ticket.status === 'open' && styles.statusOpen],
846
+ children: ticket.status
847
+ })
848
+ })]
849
+ }, ticket.id);
850
+ })
851
+ }), mode === 'human' && selectedTicketId && null, mode === 'voice' && /*#__PURE__*/_jsx(VoiceControlsRow, {
852
+ isMicActive: isMicActive,
853
+ isSpeakerMuted: isSpeakerMuted,
854
+ onMicToggle: onMicToggle || (() => {}),
855
+ onSpeakerToggle: onSpeakerToggle || (() => {}),
856
+ isAISpeaking: isAISpeaking,
857
+ isVoiceConnected: isVoiceConnected,
858
+ isArabic: isArabic,
859
+ onStopSession: onStopSession
860
+ })]
525
861
  })]
526
862
  });
527
863
  }
@@ -552,6 +888,71 @@ const styles = StyleSheet.create({
552
888
  fabIcon: {
553
889
  fontSize: 28
554
890
  },
891
+ unreadPopup: {
892
+ position: 'absolute',
893
+ bottom: 70,
894
+ // Float above the FAB
895
+ left: -70,
896
+ // Centered over a 60px FAB
897
+ width: 200,
898
+ backgroundColor: '#fff',
899
+ borderRadius: 16,
900
+ padding: 12,
901
+ elevation: 6,
902
+ shadowColor: '#000',
903
+ shadowOffset: {
904
+ width: 0,
905
+ height: 4
906
+ },
907
+ shadowOpacity: 0.25,
908
+ shadowRadius: 5
909
+ },
910
+ unreadPopupLTR: {
911
+ borderBottomLeftRadius: 4
912
+ },
913
+ unreadPopupRTL: {
914
+ borderBottomRightRadius: 4
915
+ },
916
+ unreadPopupText: {
917
+ color: '#000',
918
+ fontSize: 14,
919
+ fontWeight: '500',
920
+ lineHeight: 20
921
+ },
922
+ statusPopup: {
923
+ position: 'absolute',
924
+ bottom: 70,
925
+ left: -70,
926
+ width: 220,
927
+ minHeight: 48,
928
+ backgroundColor: '#fff',
929
+ borderRadius: 16,
930
+ paddingHorizontal: 12,
931
+ paddingVertical: 10,
932
+ elevation: 6,
933
+ shadowColor: '#000',
934
+ shadowOffset: {
935
+ width: 0,
936
+ height: 4
937
+ },
938
+ shadowOpacity: 0.2,
939
+ shadowRadius: 5,
940
+ flexDirection: 'row',
941
+ alignItems: 'center',
942
+ gap: 8
943
+ },
944
+ statusPopupText: {
945
+ color: '#111827',
946
+ fontSize: 13,
947
+ fontWeight: '600',
948
+ lineHeight: 18,
949
+ flex: 1
950
+ },
951
+ popupBadgeOverride: {
952
+ top: -8,
953
+ right: -8,
954
+ borderColor: '#fff'
955
+ },
555
956
  expandedContainer: {
556
957
  position: 'absolute',
557
958
  zIndex: 9999,
@@ -586,35 +987,98 @@ const styles = StyleSheet.create({
586
987
  position: 'absolute',
587
988
  right: 0,
588
989
  top: 0,
589
- padding: 8
990
+ padding: 12,
991
+ zIndex: 20 // ensure it sits above the drag corner
590
992
  },
591
993
  minimizeText: {
592
994
  color: '#fff',
593
995
  fontSize: 18,
594
996
  fontWeight: 'bold'
595
997
  },
596
- resultBubble: {
597
- borderRadius: 12,
998
+ // ── Corner drag handles ────────────────────────────────────────
999
+ cornerHandle: {
1000
+ position: 'absolute',
1001
+ width: 32,
1002
+ height: 32,
1003
+ zIndex: 10
1004
+ },
1005
+ cornerTL: {
1006
+ top: 0,
1007
+ left: 0
1008
+ },
1009
+ cornerTR: {
1010
+ top: 0,
1011
+ right: 0
1012
+ },
1013
+ cornerBL: {
1014
+ bottom: 0,
1015
+ left: 0
1016
+ },
1017
+ cornerBR: {
1018
+ bottom: 0,
1019
+ right: 0
1020
+ },
1021
+ // Subtle L-shaped indicator so users know the corners are draggable
1022
+ cornerIndicator: {
1023
+ position: 'absolute',
1024
+ backgroundColor: 'rgba(255, 255, 255, 0.25)'
1025
+ },
1026
+ cornerIndicatorTL: {
1027
+ top: 6,
1028
+ left: 6,
1029
+ width: 10,
1030
+ height: 2,
1031
+ borderTopLeftRadius: 1
1032
+ },
1033
+ cornerIndicatorTR: {
1034
+ top: 6,
1035
+ right: 6,
1036
+ width: 10,
1037
+ height: 2,
1038
+ borderTopRightRadius: 1
1039
+ },
1040
+ cornerIndicatorBL: {
1041
+ bottom: 6,
1042
+ left: 6,
1043
+ width: 10,
1044
+ height: 2,
1045
+ borderBottomLeftRadius: 1
1046
+ },
1047
+ cornerIndicatorBR: {
1048
+ bottom: 6,
1049
+ right: 6,
1050
+ width: 10,
1051
+ height: 2,
1052
+ borderBottomRightRadius: 1
1053
+ },
1054
+ messageList: {
1055
+ marginBottom: 12
1056
+ },
1057
+ messageBubble: {
598
1058
  padding: 12,
599
- marginBottom: 12,
600
- flexDirection: 'row',
601
- alignItems: 'flex-start'
1059
+ borderRadius: 16,
1060
+ marginBottom: 8,
1061
+ maxWidth: '85%'
602
1062
  },
603
- resultSuccess: {
604
- backgroundColor: 'rgba(40, 167, 69, 0.2)'
1063
+ messageBubbleUser: {
1064
+ alignSelf: 'flex-end',
1065
+ backgroundColor: '#7B68EE',
1066
+ borderBottomRightRadius: 4
605
1067
  },
606
- resultError: {
607
- backgroundColor: 'rgba(220, 53, 69, 0.2)'
1068
+ messageBubbleAI: {
1069
+ alignSelf: 'flex-start',
1070
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
1071
+ borderBottomLeftRadius: 4
608
1072
  },
609
- resultText: {
610
- color: '#fff',
1073
+ messageText: {
611
1074
  fontSize: 14,
612
- lineHeight: 20,
613
- flex: 1
1075
+ lineHeight: 20
614
1076
  },
615
- resultScroll: {
616
- maxHeight: 200,
617
- flex: 1
1077
+ messageTextUser: {
1078
+ color: '#fff'
1079
+ },
1080
+ messageTextAI: {
1081
+ color: '#fff' // Or slightly off-white for contrast
618
1082
  },
619
1083
  dismissButton: {
620
1084
  marginLeft: 8,
@@ -627,27 +1091,74 @@ const styles = StyleSheet.create({
627
1091
  },
628
1092
  inputRow: {
629
1093
  flexDirection: 'row',
630
- alignItems: 'center',
1094
+ alignItems: 'flex-end',
631
1095
  gap: 8,
632
- justifyContent: 'center'
1096
+ justifyContent: 'center',
1097
+ paddingBottom: 2 // Slight padding so buttons don't clip against bottom edge
1098
+ },
1099
+ approvalPanel: {
1100
+ marginBottom: 10,
1101
+ gap: 8
1102
+ },
1103
+ approvalHint: {
1104
+ color: 'rgba(255,255,255,0.72)',
1105
+ fontSize: 12,
1106
+ lineHeight: 18,
1107
+ textAlign: 'center'
1108
+ },
1109
+ approvalActions: {
1110
+ flexDirection: 'row',
1111
+ gap: 8
1112
+ },
1113
+ approvalActionBtn: {
1114
+ flex: 1,
1115
+ minHeight: 42,
1116
+ borderRadius: 14,
1117
+ alignItems: 'center',
1118
+ justifyContent: 'center',
1119
+ paddingHorizontal: 10
1120
+ },
1121
+ approvalActionPrimary: {
1122
+ backgroundColor: '#7B68EE'
1123
+ },
1124
+ approvalActionNeutral: {
1125
+ backgroundColor: 'rgba(255,255,255,0.16)'
1126
+ },
1127
+ approvalActionSecondary: {
1128
+ backgroundColor: 'rgba(255,255,255,0.08)'
1129
+ },
1130
+ approvalActionText: {
1131
+ color: '#ffffff',
1132
+ fontSize: 13,
1133
+ fontWeight: '700',
1134
+ textAlign: 'center'
1135
+ },
1136
+ approvalActionPrimaryText: {
1137
+ color: '#ffffff'
1138
+ },
1139
+ approvalActionSecondaryText: {
1140
+ color: 'rgba(255,255,255,0.82)'
633
1141
  },
634
1142
  input: {
635
1143
  flex: 1,
636
1144
  backgroundColor: 'rgba(255, 255, 255, 0.1)',
637
- borderRadius: 20,
638
- paddingHorizontal: 16,
639
- paddingVertical: 10,
1145
+ borderRadius: 24,
1146
+ paddingHorizontal: 18,
1147
+ paddingTop: 14,
1148
+ paddingBottom: 14,
640
1149
  color: '#fff',
641
- fontSize: 16
1150
+ fontSize: 16,
1151
+ minHeight: 48,
1152
+ maxHeight: 120 // wrap up to ~5 lines before scrolling internal
642
1153
  },
643
1154
  inputRTL: {
644
1155
  textAlign: 'right',
645
1156
  writingDirection: 'rtl'
646
1157
  },
647
1158
  sendButton: {
648
- width: 40,
649
- height: 40,
650
- borderRadius: 20,
1159
+ width: 44,
1160
+ height: 44,
1161
+ borderRadius: 22,
651
1162
  backgroundColor: 'rgba(255, 255, 255, 0.15)',
652
1163
  justifyContent: 'center',
653
1164
  alignItems: 'center'
@@ -659,9 +1170,9 @@ const styles = StyleSheet.create({
659
1170
  fontSize: 18
660
1171
  },
661
1172
  dictationButton: {
662
- width: 40,
663
- height: 40,
664
- borderRadius: 20,
1173
+ width: 44,
1174
+ height: 44,
1175
+ borderRadius: 22,
665
1176
  backgroundColor: 'rgba(255, 255, 255, 0.15)',
666
1177
  justifyContent: 'center',
667
1178
  alignItems: 'center'
@@ -979,4 +1490,187 @@ const audioStyles = StyleSheet.create({
979
1490
  fontSize: 14
980
1491
  }
981
1492
  });
1493
+ // ─── Relative Date Helper ────────────────────────────────────────────────────
1494
+
1495
+ function getRelativeDate(timestampMs) {
1496
+ const now = Date.now();
1497
+ const diff = now - timestampMs;
1498
+ const minutes = Math.floor(diff / 60000);
1499
+ const hours = Math.floor(diff / 3600000);
1500
+ const days = Math.floor(diff / 86400000);
1501
+ if (minutes < 1) return 'Just now';
1502
+ if (minutes < 60) return `${minutes}m ago`;
1503
+ if (hours < 24) return `${hours}h ago`;
1504
+ if (days === 1) return 'Yesterday';
1505
+ if (days < 7) return `${days} days ago`;
1506
+ return new Date(timestampMs).toLocaleDateString(undefined, {
1507
+ month: 'short',
1508
+ day: 'numeric'
1509
+ });
1510
+ }
1511
+
1512
+ // ─── History Styles ───────────────────────────────────────────────────────
1513
+
1514
+ const historyStyles = StyleSheet.create({
1515
+ headerActions: {
1516
+ position: 'absolute',
1517
+ left: 16,
1518
+ top: 12,
1519
+ flexDirection: 'row',
1520
+ alignItems: 'center',
1521
+ gap: 8,
1522
+ zIndex: 20
1523
+ },
1524
+ // ─ History trigger button
1525
+ historyBtn: {
1526
+ flexDirection: 'row',
1527
+ alignItems: 'center',
1528
+ padding: 4
1529
+ },
1530
+ quickNewBtn: {
1531
+ width: 24,
1532
+ height: 24,
1533
+ borderRadius: 12,
1534
+ backgroundColor: 'rgba(255,255,255,0.08)',
1535
+ alignItems: 'center',
1536
+ justifyContent: 'center'
1537
+ },
1538
+ quickNewBtnText: {
1539
+ color: 'rgba(255,255,255,0.78)',
1540
+ fontSize: 18,
1541
+ lineHeight: 18,
1542
+ fontWeight: '500',
1543
+ marginTop: -1
1544
+ },
1545
+ historyCountBadge: {
1546
+ marginLeft: 3,
1547
+ backgroundColor: 'rgba(123,104,238,0.8)',
1548
+ borderRadius: 8,
1549
+ paddingHorizontal: 5,
1550
+ paddingVertical: 1
1551
+ },
1552
+ historyCountBadgeText: {
1553
+ color: '#fff',
1554
+ fontSize: 10,
1555
+ fontWeight: '700',
1556
+ lineHeight: 14
1557
+ },
1558
+ // ─ Panel container
1559
+ panel: {
1560
+ flex: 1
1561
+ },
1562
+ // ─ Header row (Back | History | + New)
1563
+ headerRow: {
1564
+ flexDirection: 'row',
1565
+ alignItems: 'center',
1566
+ justifyContent: 'space-between',
1567
+ marginBottom: 12,
1568
+ paddingHorizontal: 4
1569
+ },
1570
+ backBtn: {
1571
+ padding: 4
1572
+ },
1573
+ backBtnText: {
1574
+ color: '#7B68EE',
1575
+ fontSize: 13,
1576
+ fontWeight: '600'
1577
+ },
1578
+ headerTitle: {
1579
+ color: '#fff',
1580
+ fontSize: 15,
1581
+ fontWeight: '700',
1582
+ letterSpacing: 0.3
1583
+ },
1584
+ newBtn: {
1585
+ backgroundColor: 'rgba(123,104,238,0.25)',
1586
+ borderRadius: 12,
1587
+ paddingHorizontal: 10,
1588
+ paddingVertical: 5
1589
+ },
1590
+ newBtnText: {
1591
+ color: '#7B68EE',
1592
+ fontSize: 12,
1593
+ fontWeight: '700'
1594
+ },
1595
+ // ─ Shimmer loading cards
1596
+ shimmerWrap: {
1597
+ gap: 8
1598
+ },
1599
+ shimmerCard: {
1600
+ backgroundColor: 'rgba(255,255,255,0.06)',
1601
+ borderRadius: 12,
1602
+ padding: 14,
1603
+ height: 64,
1604
+ justifyContent: 'center'
1605
+ },
1606
+ shimmerLine: {
1607
+ height: 10,
1608
+ borderRadius: 5,
1609
+ backgroundColor: 'rgba(255,255,255,0.12)'
1610
+ },
1611
+ // ─ Empty state
1612
+ emptyWrap: {
1613
+ alignItems: 'center',
1614
+ paddingVertical: 32,
1615
+ gap: 6
1616
+ },
1617
+ emptyTitle: {
1618
+ color: 'rgba(255,255,255,0.7)',
1619
+ fontSize: 15,
1620
+ fontWeight: '600'
1621
+ },
1622
+ emptySubtitle: {
1623
+ color: 'rgba(255,255,255,0.35)',
1624
+ fontSize: 12,
1625
+ textAlign: 'center'
1626
+ },
1627
+ // ─ Conversation cards
1628
+ convCard: {
1629
+ backgroundColor: 'rgba(255,255,255,0.07)',
1630
+ borderRadius: 14,
1631
+ padding: 14,
1632
+ marginBottom: 8,
1633
+ borderWidth: 1,
1634
+ borderColor: 'rgba(255,255,255,0.06)'
1635
+ },
1636
+ convCardPressed: {
1637
+ backgroundColor: 'rgba(123,104,238,0.18)',
1638
+ borderColor: 'rgba(123,104,238,0.3)'
1639
+ },
1640
+ convCardTop: {
1641
+ flexDirection: 'row',
1642
+ alignItems: 'center',
1643
+ justifyContent: 'space-between',
1644
+ marginBottom: 4
1645
+ },
1646
+ convTitle: {
1647
+ color: '#fff',
1648
+ fontSize: 14,
1649
+ fontWeight: '600',
1650
+ flex: 1,
1651
+ marginRight: 8
1652
+ },
1653
+ convMsgBadge: {
1654
+ backgroundColor: 'rgba(255,255,255,0.12)',
1655
+ borderRadius: 8,
1656
+ paddingHorizontal: 7,
1657
+ paddingVertical: 2
1658
+ },
1659
+ convMsgBadgeText: {
1660
+ color: 'rgba(255,255,255,0.6)',
1661
+ fontSize: 10,
1662
+ fontWeight: '600'
1663
+ },
1664
+ convPreview: {
1665
+ color: 'rgba(255,255,255,0.45)',
1666
+ fontSize: 12,
1667
+ lineHeight: 16,
1668
+ marginBottom: 6
1669
+ },
1670
+ convDate: {
1671
+ color: 'rgba(255,255,255,0.3)',
1672
+ fontSize: 11,
1673
+ fontWeight: '500'
1674
+ }
1675
+ });
982
1676
  //# sourceMappingURL=AgentChatBar.js.map