@mobileai/react-native 0.9.17 → 0.9.18

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 (213) hide show
  1. package/package.json +2 -5
  2. package/lib/module/__cli_tmp__.js.map +0 -1
  3. package/lib/module/components/AIAgent.js.map +0 -1
  4. package/lib/module/components/AIZone.js.map +0 -1
  5. package/lib/module/components/AgentChatBar.js.map +0 -1
  6. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  7. package/lib/module/components/AgentOverlay.js.map +0 -1
  8. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  9. package/lib/module/components/HighlightOverlay.js.map +0 -1
  10. package/lib/module/components/Icons.js.map +0 -1
  11. package/lib/module/components/ProactiveHint.js.map +0 -1
  12. package/lib/module/components/cards/InfoCard.js.map +0 -1
  13. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  14. package/lib/module/config/endpoints.js.map +0 -1
  15. package/lib/module/core/ActionRegistry.js.map +0 -1
  16. package/lib/module/core/AgentRuntime.js.map +0 -1
  17. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  18. package/lib/module/core/IdleDetector.js.map +0 -1
  19. package/lib/module/core/MCPBridge.js.map +0 -1
  20. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  21. package/lib/module/core/ZoneRegistry.js.map +0 -1
  22. package/lib/module/core/systemPrompt.js.map +0 -1
  23. package/lib/module/core/types.js.map +0 -1
  24. package/lib/module/hooks/useAction.js.map +0 -1
  25. package/lib/module/index.js.map +0 -1
  26. package/lib/module/plugin/withAppIntents.js.map +0 -1
  27. package/lib/module/providers/GeminiProvider.js.map +0 -1
  28. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  29. package/lib/module/providers/ProviderFactory.js.map +0 -1
  30. package/lib/module/services/AudioInputService.js.map +0 -1
  31. package/lib/module/services/AudioOutputService.js.map +0 -1
  32. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  33. package/lib/module/services/VoiceService.js.map +0 -1
  34. package/lib/module/services/flags/FlagService.js.map +0 -1
  35. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  36. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  37. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  38. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  39. package/lib/module/services/telemetry/device.js.map +0 -1
  40. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  41. package/lib/module/services/telemetry/index.js.map +0 -1
  42. package/lib/module/services/telemetry/types.js.map +0 -1
  43. package/lib/module/support/CSATSurvey.js.map +0 -1
  44. package/lib/module/support/EscalationEventSource.js.map +0 -1
  45. package/lib/module/support/EscalationSocket.js.map +0 -1
  46. package/lib/module/support/SupportChatModal.js.map +0 -1
  47. package/lib/module/support/SupportGreeting.js.map +0 -1
  48. package/lib/module/support/TicketStore.js.map +0 -1
  49. package/lib/module/support/escalateTool.js.map +0 -1
  50. package/lib/module/support/index.js.map +0 -1
  51. package/lib/module/support/supportPrompt.js.map +0 -1
  52. package/lib/module/support/types.js.map +0 -1
  53. package/lib/module/tools/datePickerTool.js.map +0 -1
  54. package/lib/module/tools/guideTool.js.map +0 -1
  55. package/lib/module/tools/index.js.map +0 -1
  56. package/lib/module/tools/keyboardTool.js.map +0 -1
  57. package/lib/module/tools/longPressTool.js.map +0 -1
  58. package/lib/module/tools/pickerTool.js.map +0 -1
  59. package/lib/module/tools/restoreTool.js.map +0 -1
  60. package/lib/module/tools/scrollTool.js.map +0 -1
  61. package/lib/module/tools/simplifyTool.js.map +0 -1
  62. package/lib/module/tools/sliderTool.js.map +0 -1
  63. package/lib/module/tools/tapTool.js.map +0 -1
  64. package/lib/module/tools/typeTool.js.map +0 -1
  65. package/lib/module/tools/types.js.map +0 -1
  66. package/lib/module/types/jsx.d.js.map +0 -1
  67. package/lib/module/utils/audioUtils.js.map +0 -1
  68. package/lib/module/utils/logger.js.map +0 -1
  69. package/lib/typescript/babel.config.d.ts.map +0 -1
  70. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  71. package/lib/typescript/eslint.config.d.mts.map +0 -1
  72. package/lib/typescript/generate-map.d.ts.map +0 -1
  73. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  74. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  75. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  76. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  77. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  78. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  79. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  80. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  81. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  82. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  83. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  84. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  85. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  86. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  87. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  88. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  89. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  90. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  91. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  92. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  93. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  94. package/lib/typescript/src/core/types.d.ts.map +0 -1
  95. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  96. package/lib/typescript/src/index.d.ts.map +0 -1
  97. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  98. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  99. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  100. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  101. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  102. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  103. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  104. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  105. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  106. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  107. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  108. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  109. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  110. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  111. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  112. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  113. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  114. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  115. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  116. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  117. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  118. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  119. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  120. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  121. package/lib/typescript/src/support/index.d.ts.map +0 -1
  122. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  123. package/lib/typescript/src/support/types.d.ts.map +0 -1
  124. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  125. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  126. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  127. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  128. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  129. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  130. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  131. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  132. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  133. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  134. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  135. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  136. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  137. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  139. package/src/__cli_tmp__.tsx +0 -9
  140. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  141. package/src/cli/extractors/ai-extractor.ts +0 -6
  142. package/src/cli/extractors/ast-extractor.ts +0 -551
  143. package/src/cli/generate-intents.ts +0 -140
  144. package/src/cli/generate-map.ts +0 -121
  145. package/src/cli/generate-swift.ts +0 -116
  146. package/src/cli/scanners/expo-scanner.ts +0 -203
  147. package/src/cli/scanners/rn-scanner.ts +0 -445
  148. package/src/components/AIAgent.tsx +0 -1716
  149. package/src/components/AIZone.tsx +0 -147
  150. package/src/components/AgentChatBar.tsx +0 -1143
  151. package/src/components/AgentErrorBoundary.tsx +0 -78
  152. package/src/components/AgentOverlay.tsx +0 -73
  153. package/src/components/DiscoveryTooltip.tsx +0 -148
  154. package/src/components/HighlightOverlay.tsx +0 -136
  155. package/src/components/Icons.tsx +0 -253
  156. package/src/components/ProactiveHint.tsx +0 -145
  157. package/src/components/cards/InfoCard.tsx +0 -58
  158. package/src/components/cards/ReviewSummary.tsx +0 -76
  159. package/src/config/endpoints.ts +0 -22
  160. package/src/core/ActionRegistry.ts +0 -105
  161. package/src/core/AgentRuntime.ts +0 -1471
  162. package/src/core/FiberTreeWalker.ts +0 -930
  163. package/src/core/IdleDetector.ts +0 -72
  164. package/src/core/MCPBridge.ts +0 -163
  165. package/src/core/ScreenDehydrator.ts +0 -53
  166. package/src/core/ZoneRegistry.ts +0 -44
  167. package/src/core/systemPrompt.ts +0 -431
  168. package/src/core/types.ts +0 -521
  169. package/src/hooks/useAction.ts +0 -182
  170. package/src/index.ts +0 -83
  171. package/src/plugin/withAppIntents.ts +0 -98
  172. package/src/providers/GeminiProvider.ts +0 -357
  173. package/src/providers/OpenAIProvider.ts +0 -379
  174. package/src/providers/ProviderFactory.ts +0 -36
  175. package/src/services/AudioInputService.ts +0 -226
  176. package/src/services/AudioOutputService.ts +0 -236
  177. package/src/services/KnowledgeBaseService.ts +0 -156
  178. package/src/services/VoiceService.ts +0 -451
  179. package/src/services/flags/FlagService.ts +0 -137
  180. package/src/services/telemetry/MobileAI.ts +0 -66
  181. package/src/services/telemetry/PiiScrubber.ts +0 -17
  182. package/src/services/telemetry/TelemetryService.ts +0 -323
  183. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  184. package/src/services/telemetry/device.ts +0 -93
  185. package/src/services/telemetry/deviceMetadata.ts +0 -13
  186. package/src/services/telemetry/index.ts +0 -13
  187. package/src/services/telemetry/types.ts +0 -75
  188. package/src/support/CSATSurvey.tsx +0 -304
  189. package/src/support/EscalationEventSource.ts +0 -190
  190. package/src/support/EscalationSocket.ts +0 -152
  191. package/src/support/SupportChatModal.tsx +0 -563
  192. package/src/support/SupportGreeting.tsx +0 -161
  193. package/src/support/TicketStore.ts +0 -100
  194. package/src/support/escalateTool.ts +0 -174
  195. package/src/support/index.ts +0 -29
  196. package/src/support/supportPrompt.ts +0 -55
  197. package/src/support/types.ts +0 -155
  198. package/src/tools/datePickerTool.ts +0 -60
  199. package/src/tools/guideTool.ts +0 -76
  200. package/src/tools/index.ts +0 -20
  201. package/src/tools/keyboardTool.ts +0 -30
  202. package/src/tools/longPressTool.ts +0 -61
  203. package/src/tools/pickerTool.ts +0 -115
  204. package/src/tools/restoreTool.ts +0 -33
  205. package/src/tools/scrollTool.ts +0 -156
  206. package/src/tools/simplifyTool.ts +0 -33
  207. package/src/tools/sliderTool.ts +0 -65
  208. package/src/tools/tapTool.ts +0 -93
  209. package/src/tools/typeTool.ts +0 -113
  210. package/src/tools/types.ts +0 -58
  211. package/src/types/jsx.d.ts +0 -20
  212. package/src/utils/audioUtils.ts +0 -54
  213. package/src/utils/logger.ts +0 -38
@@ -1,1143 +0,0 @@
1
- /**
2
- * AgentChatBar — Floating, draggable, compressible chat widget.
3
- * Supports two modes: Text and Voice.
4
- * Does not block underlying UI natively.
5
- */
6
-
7
- import { useState, useRef, useEffect } from 'react';
8
- import {
9
- View,
10
- TextInput,
11
- Pressable,
12
- Text,
13
- StyleSheet,
14
- Animated,
15
- PanResponder,
16
- ScrollView,
17
- Keyboard,
18
- Platform,
19
- useWindowDimensions,
20
- } from 'react-native';
21
- import type { ExecutionResult, AgentMode, ChatBarTheme, AIMessage } from '../core/types';
22
- import {
23
- MicIcon,
24
- SpeakerIcon,
25
- SendArrowIcon,
26
- StopIcon,
27
- LoadingDots,
28
- AIBadge,
29
- CloseIcon,
30
- } from './Icons';
31
- import type { SupportTicket } from '../support/types';
32
- import { logger } from '../utils/logger';
33
- import { DiscoveryTooltip } from './DiscoveryTooltip';
34
-
35
- // ─── Props ─────────────────────────────────────────────────────
36
-
37
- interface AgentChatBarProps {
38
- onSend: (message: string) => void;
39
- isThinking: boolean;
40
- lastResult: ExecutionResult | null;
41
- language: 'en' | 'ar';
42
- onDismiss?: () => void;
43
- /** Available modes (default: ['text']) */
44
- availableModes?: AgentMode[];
45
- /** Current active mode */
46
- mode?: AgentMode;
47
- onModeChange?: (mode: AgentMode) => void;
48
- /** Voice controls */
49
- onMicToggle?: (active: boolean) => void;
50
- onSpeakerToggle?: (muted: boolean) => void;
51
- isMicActive?: boolean;
52
- isSpeakerMuted?: boolean;
53
- /** AI is currently speaking */
54
- isAISpeaking?: boolean;
55
- /** Voice WebSocket is connected */
56
- isVoiceConnected?: boolean;
57
- /** Live human agent is typing */
58
- /** Live human agent is typing */
59
- isAgentTyping?: boolean; // used by SupportChatModal via AIAgent
60
- /** Full session cleanup (stop mic, audio, WebSocket, live mode) */
61
- onStopSession?: () => void;
62
- /** Color theme overrides */
63
- theme?: ChatBarTheme;
64
- /** Active support tickets (for human mode) */
65
- tickets?: SupportTicket[];
66
- /** Currently selected ticket ID */
67
- selectedTicketId?: string | null;
68
- /** Callback when user selects a ticket */
69
- onTicketSelect?: (ticketId: string) => void;
70
- /** Callback when user goes back to ticket list */
71
- onBackToTickets?: () => void;
72
- /** Incremented to trigger auto-expand */
73
- autoExpandTrigger?: number;
74
- /** Chat messages for selected ticket */
75
- chatMessages?: AIMessage[];
76
- /** The user's original typed query — shown in the result bubble instead of agent reasoning */
77
- lastUserMessage?: string | null;
78
- /** Unread message counts per ticket (ticketId -> count) */
79
- unreadCounts?: Record<string, number>;
80
- /** Total unread messages across all tickets */
81
- totalUnread?: number;
82
- /** Show first-use discovery tooltip above FAB */
83
- showDiscoveryTooltip?: boolean;
84
- /** Called when discovery tooltip is dismissed */
85
- onTooltipDismiss?: () => void;
86
- }
87
-
88
- // ─── Mode Selector ─────────────────────────────────────────────
89
-
90
- function ModeSelector({
91
- modes,
92
- activeMode,
93
- onSelect,
94
- isArabic = false,
95
- totalUnread = 0,
96
- }: {
97
- modes: AgentMode[];
98
- activeMode: AgentMode;
99
- onSelect: (mode: AgentMode) => void;
100
- isArabic?: boolean;
101
- totalUnread?: number;
102
- }) {
103
- if (modes.length <= 1) return null;
104
-
105
- const labels: Record<AgentMode, { label: string }> = {
106
- text: { label: isArabic ? 'نص' : 'Text' },
107
- voice: { label: isArabic ? 'صوت' : 'Voice' },
108
- human: { label: isArabic ? 'دعم' : 'Human' },
109
- };
110
-
111
- const dotColor: Record<AgentMode, string> = {
112
- text: '#7B68EE',
113
- voice: '#34C759',
114
- human: '#FF9500',
115
- };
116
-
117
- return (
118
- <View style={modeStyles.container}>
119
- {modes.map((mode) => (
120
- <Pressable
121
- key={mode}
122
- style={[
123
- modeStyles.tab,
124
- activeMode === mode && modeStyles.tabActive,
125
- ]}
126
- onPress={() => onSelect(mode)}
127
- accessibilityLabel={`Switch to ${labels[mode].label} mode`}
128
- >
129
- {/* Active indicator dot */}
130
- {activeMode === mode && (
131
- <View style={{
132
- width: 6,
133
- height: 6,
134
- borderRadius: 3,
135
- backgroundColor: dotColor[mode],
136
- }} />
137
- )}
138
- <Text
139
- style={[
140
- modeStyles.tabLabel,
141
- activeMode === mode && modeStyles.tabLabelActive,
142
- ]}
143
- >
144
- {labels[mode].label}
145
- </Text>
146
- {/* Unread indicator — inline after label */}
147
- {mode === 'human' && totalUnread > 0 && (
148
- <View style={styles.humanTabBadge}>
149
- <Text style={styles.humanTabBadgeText}>
150
- {totalUnread > 99 ? '99+' : totalUnread}
151
- </Text>
152
- </View>
153
- )}
154
- </Pressable>
155
- ))}
156
- </View>
157
- );
158
- }
159
-
160
- // ─── Audio Control Button ──────────────────────────────────────
161
-
162
- function AudioControlButton({
163
- children,
164
- isActive,
165
- onPress,
166
- label,
167
- size = 36,
168
- }: {
169
- children: React.ReactNode;
170
- isActive: boolean;
171
- onPress: () => void;
172
- label: string;
173
- size?: number;
174
- }) {
175
- return (
176
- <Pressable
177
- style={[
178
- audioStyles.controlBtn,
179
- { width: size, height: size, borderRadius: size / 2 },
180
- isActive && audioStyles.controlBtnActive,
181
- ]}
182
- onPress={onPress}
183
- accessibilityLabel={label}
184
- hitSlop={8}
185
- >
186
- {children}
187
- </Pressable>
188
- );
189
- }
190
-
191
- // ─── Dictation Button (optional expo-speech-recognition) ──────
192
-
193
- /**
194
- * Try to load expo-speech-recognition as an optional peer dependency.
195
- * If not installed, returns null and the mic button won't render.
196
- * Same pattern as react-native-view-shot for screenshots.
197
- */
198
- let SpeechModule: any = null;
199
- try {
200
- // Static require — Metro needs a literal string for bundling.
201
- SpeechModule = require('expo-speech-recognition');
202
- } catch {
203
- // Not installed — dictation button won't appear
204
- }
205
-
206
- function DictationButton({
207
- language,
208
- onTranscript,
209
- disabled,
210
- }: {
211
- language: string;
212
- onTranscript: (text: string) => void;
213
- disabled: boolean;
214
- }) {
215
- const [isListening, setIsListening] = useState(false);
216
-
217
- // Don't render if expo-speech-recognition isn't installed
218
- if (!SpeechModule) return null;
219
-
220
- const { ExpoSpeechRecognitionModule } = SpeechModule;
221
- if (!ExpoSpeechRecognitionModule) return null;
222
-
223
- const toggle = async () => {
224
- if (isListening) {
225
- ExpoSpeechRecognitionModule.stop();
226
- return;
227
- }
228
-
229
- try {
230
- const perms = await ExpoSpeechRecognitionModule.requestPermissionsAsync();
231
- if (!perms.granted) return;
232
-
233
- // Register one-shot listeners for this recording session
234
- const resultListener = ExpoSpeechRecognitionModule.addListener(
235
- 'result',
236
- (event: any) => {
237
- const transcript = event.results?.[0]?.transcript;
238
- if (transcript && event.isFinal) {
239
- onTranscript(transcript);
240
- }
241
- },
242
- );
243
-
244
- const endListener = ExpoSpeechRecognitionModule.addListener(
245
- 'end',
246
- () => {
247
- setIsListening(false);
248
- resultListener.remove();
249
- endListener.remove();
250
- },
251
- );
252
-
253
- ExpoSpeechRecognitionModule.start({
254
- lang: language === 'ar' ? 'ar-SA' : 'en-US',
255
- interimResults: false,
256
- continuous: false,
257
- addsPunctuation: true,
258
- });
259
-
260
- setIsListening(true);
261
- } catch {
262
- setIsListening(false);
263
- }
264
- };
265
-
266
- return (
267
- <Pressable
268
- style={[
269
- styles.dictationButton,
270
- isListening && styles.dictationButtonActive,
271
- disabled && styles.sendButtonDisabled,
272
- ]}
273
- onPress={toggle}
274
- disabled={disabled}
275
- accessibilityLabel={isListening ? 'Stop dictation' : 'Start dictation'}
276
- hitSlop={8}
277
- >
278
- {isListening ? <StopIcon size={18} color="#FF3B30" /> : <MicIcon size={18} color="#fff" />}
279
- </Pressable>
280
- );
281
- }
282
-
283
- // ─── Text Input Row ────────────────────────────────────────────
284
-
285
- function TextInputRow({
286
- text,
287
- setText,
288
- onSend,
289
- isThinking,
290
- isArabic,
291
- theme,
292
- }: {
293
- text: string;
294
- setText: (t: string) => void;
295
- onSend: () => void;
296
- isThinking: boolean;
297
- isArabic: boolean;
298
- theme?: ChatBarTheme;
299
- }) {
300
- return (
301
- <View style={styles.inputRow}>
302
- <TextInput
303
- style={[
304
- styles.input,
305
- isArabic && styles.inputRTL,
306
- theme?.inputBackgroundColor ? { backgroundColor: theme.inputBackgroundColor } : undefined,
307
- theme?.textColor ? { color: theme.textColor } : undefined,
308
- ]}
309
- placeholder={isArabic ? 'اكتب طلبك...' : 'Ask AI...'}
310
- placeholderTextColor={theme?.textColor ? `${theme.textColor}66` : '#999'}
311
- value={text}
312
- onChangeText={setText}
313
- onSubmitEditing={onSend}
314
- returnKeyType="send"
315
- editable={!isThinking}
316
- multiline={false}
317
- />
318
- <DictationButton
319
- language={isArabic ? 'ar' : 'en'}
320
- onTranscript={(t: string) => setText(t)}
321
- disabled={isThinking}
322
- />
323
- <Pressable
324
- style={[
325
- styles.sendButton,
326
- isThinking && styles.sendButtonDisabled,
327
- theme?.primaryColor ? { backgroundColor: theme.primaryColor } : undefined,
328
- ]}
329
- onPress={onSend}
330
- disabled={isThinking || !text.trim()}
331
- accessibilityLabel="Send request to AI Agent"
332
- >
333
- {isThinking ? <LoadingDots size={18} color={theme?.textColor || '#fff'} /> : <SendArrowIcon size={18} color={theme?.textColor || '#fff'} />}
334
- </Pressable>
335
- </View>
336
- );
337
- }
338
-
339
- // ─── Voice Controls Row ────────────────────────────────────────
340
-
341
- function VoiceControlsRow({
342
- isMicActive,
343
- isSpeakerMuted,
344
- onMicToggle,
345
- onSpeakerToggle,
346
- isAISpeaking,
347
- isVoiceConnected = false,
348
- isArabic,
349
- onStopSession,
350
- }: {
351
- isMicActive: boolean;
352
- isSpeakerMuted: boolean;
353
- onMicToggle: (active: boolean) => void;
354
- onSpeakerToggle: (muted: boolean) => void;
355
- isAISpeaking?: boolean;
356
- isVoiceConnected?: boolean;
357
- isArabic: boolean;
358
- onStopSession?: () => void;
359
- }) {
360
- const isConnecting = !isVoiceConnected;
361
-
362
- return (
363
- <View style={styles.inputRow}>
364
- {/* Speaker mute/unmute */}
365
- <AudioControlButton
366
- isActive={isSpeakerMuted}
367
- onPress={() => onSpeakerToggle(!isSpeakerMuted)}
368
- label={isSpeakerMuted ? 'Unmute speaker' : 'Mute speaker'}
369
- >
370
- <SpeakerIcon size={18} color="#fff" muted={isSpeakerMuted} />
371
- </AudioControlButton>
372
-
373
- {/* Mic button — large center */}
374
- <Pressable
375
- style={[
376
- audioStyles.micButton,
377
- isConnecting && audioStyles.micButtonConnecting,
378
- isMicActive && audioStyles.micButtonActive,
379
- isAISpeaking && audioStyles.micButtonSpeaking,
380
- ]}
381
- onPress={() => {
382
- if (isMicActive) {
383
- // Stop button: full session cleanup
384
- onStopSession?.();
385
- } else if (!isConnecting) {
386
- // Talk button: start mic
387
- onMicToggle(true);
388
- }
389
- }}
390
- disabled={isConnecting}
391
- accessibilityLabel={
392
- isConnecting ? 'Connecting...' :
393
- isMicActive ? 'Stop recording' : 'Start recording'
394
- }
395
- >
396
- <View style={audioStyles.micIconWrap}>
397
- {isConnecting
398
- ? <LoadingDots size={20} color="#fff" />
399
- : isAISpeaking
400
- ? <SpeakerIcon size={20} color="#fff" />
401
- : isMicActive
402
- ? <StopIcon size={20} color="#fff" />
403
- : <MicIcon size={20} color="#fff" />
404
- }
405
- </View>
406
- <Text style={audioStyles.micLabel}>
407
- {isConnecting
408
- ? (isArabic ? 'جاري الاتصال...' : 'Connecting...')
409
- : isAISpeaking
410
- ? (isArabic ? 'يتحدث...' : 'Speaking...')
411
- : isMicActive
412
- ? (isArabic ? 'إيقاف' : 'Stop')
413
- : (isArabic ? 'تحدث' : 'Talk')}
414
- </Text>
415
- </Pressable>
416
-
417
- {/* Connection status indicator */}
418
- <View style={[
419
- audioStyles.statusDot,
420
- isVoiceConnected ? audioStyles.statusDotConnected : audioStyles.statusDotConnecting,
421
- ]} />
422
- </View>
423
- );
424
- }
425
-
426
-
427
- // ─── Main Component ────────────────────────────────────────────
428
-
429
- export function AgentChatBar({
430
- onSend,
431
- isThinking,
432
- lastResult,
433
- language,
434
- onDismiss,
435
- availableModes = ['text'],
436
- mode = 'text',
437
- onModeChange,
438
- onMicToggle,
439
- onSpeakerToggle,
440
- isMicActive = false,
441
- isSpeakerMuted = false,
442
- isAISpeaking,
443
- isVoiceConnected,
444
- onStopSession,
445
- theme,
446
- tickets = [],
447
- selectedTicketId,
448
- onTicketSelect,
449
- autoExpandTrigger = 0,
450
- lastUserMessage,
451
- unreadCounts = {},
452
- totalUnread = 0,
453
- showDiscoveryTooltip = false,
454
- onTooltipDismiss,
455
- }: AgentChatBarProps) {
456
- const [text, setText] = useState('');
457
- const [isExpanded, setIsExpanded] = useState(false);
458
- const { height } = useWindowDimensions();
459
- const isArabic = language === 'ar';
460
-
461
- // Auto-expand when triggered (e.g. on escalation)
462
- useEffect(() => {
463
- if (autoExpandTrigger > 0) setIsExpanded(true);
464
- }, [autoExpandTrigger]);
465
-
466
- const pan = useRef(new Animated.ValueXY({ x: 10, y: height - 200 })).current;
467
- const keyboardOffset = useRef(new Animated.Value(0)).current;
468
-
469
- // ─── Keyboard Handling ──────────────────────────────────────
470
- useEffect(() => {
471
- const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
472
- const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
473
-
474
- const showSub = Keyboard.addListener(showEvent, (e) => {
475
- Animated.timing(keyboardOffset, {
476
- toValue: -e.endCoordinates.height,
477
- duration: Platform.OS === 'ios' ? e.duration || 250 : 200,
478
- useNativeDriver: false,
479
- }).start();
480
- });
481
- const hideSub = Keyboard.addListener(hideEvent, () => {
482
- Animated.timing(keyboardOffset, {
483
- toValue: 0,
484
- duration: 200,
485
- useNativeDriver: false,
486
- }).start();
487
- });
488
-
489
- return () => {
490
- showSub.remove();
491
- hideSub.remove();
492
- };
493
- }, [keyboardOffset]);
494
- const panResponder = useRef(
495
- PanResponder.create({
496
- onMoveShouldSetPanResponder: (_, gestureState) => {
497
- return Math.abs(gestureState.dx) > 5 || Math.abs(gestureState.dy) > 5;
498
- },
499
- onPanResponderGrant: () => {
500
- pan.setOffset({
501
- x: (pan.x as any)._value,
502
- y: (pan.y as any)._value,
503
- });
504
- pan.setValue({ x: 0, y: 0 });
505
- },
506
- onPanResponderMove: Animated.event(
507
- [null, { dx: pan.x, dy: pan.y }],
508
- { useNativeDriver: false }
509
- ),
510
- onPanResponderRelease: () => {
511
- pan.flattenOffset();
512
- },
513
- })
514
- ).current;
515
-
516
- const handleSend = () => {
517
- if (text.trim() && !isThinking) {
518
- onSend(text.trim());
519
- setText('');
520
- }
521
- };
522
-
523
- // ─── HEAVY DEBUG LOGGING ──────────────────────────────────────
524
- logger.info('ChatBar', '★★★ RENDER — mode:', mode,
525
- '| selectedTicketId:', selectedTicketId,
526
- '| tickets:', tickets.length,
527
- '| availableModes:', availableModes,
528
- '| lastResult:', lastResult ? lastResult.message.substring(0, 60) : 'null',
529
- '| isExpanded:', isExpanded);
530
-
531
- // ─── FAB (Compressed) ──────────────────────────────────────
532
-
533
- if (!isExpanded) {
534
- return (
535
- <Animated.View
536
- style={[styles.fabContainer, pan.getLayout()]}
537
- {...panResponder.panHandlers}
538
- >
539
- <Pressable
540
- style={[styles.fab, theme?.primaryColor ? { backgroundColor: theme.primaryColor } : undefined]}
541
- onPress={() => {
542
- onTooltipDismiss?.();
543
- setIsExpanded(true);
544
- }}
545
- accessibilityLabel={totalUnread > 0 ? `Open AI Agent Chat - ${totalUnread} unread messages` : 'Open AI Agent Chat'}
546
- >
547
- {isThinking ? <LoadingDots size={28} color={theme?.textColor || '#fff'} /> : <AIBadge size={28} />}
548
- </Pressable>
549
- {/* Discovery tooltip — shows above FAB on first use */}
550
- {showDiscoveryTooltip && (
551
- <DiscoveryTooltip
552
- language={language}
553
- primaryColor={theme?.primaryColor}
554
- onDismiss={() => onTooltipDismiss?.()}
555
- />
556
- )}
557
- {/* Unread badge on collapsed FAB */}
558
- {totalUnread > 0 && (
559
- <View style={styles.fabUnreadBadge} pointerEvents="none">
560
- <Text style={styles.fabUnreadBadgeText}>
561
- {totalUnread > 99 ? '99+' : totalUnread}
562
- </Text>
563
- </View>
564
- )}
565
- </Animated.View>
566
- );
567
- }
568
-
569
- // ─── Expanded Widget ───────────────────────────────────────
570
-
571
- return (
572
- <Animated.View style={[
573
- styles.expandedContainer,
574
- pan.getLayout(),
575
- { transform: [{ translateY: keyboardOffset }] },
576
- theme?.backgroundColor ? { backgroundColor: theme.backgroundColor } : undefined,
577
- ]}>
578
- {/* Drag Handle */}
579
- <View {...panResponder.panHandlers} style={styles.dragHandleArea} accessibilityLabel="Drag AI Agent">
580
- <View style={styles.dragGrip} />
581
- <Pressable onPress={() => setIsExpanded(false)} style={styles.minimizeBtn} accessibilityLabel="Minimize AI Agent">
582
- <Text style={styles.minimizeText}>—</Text>
583
- </Pressable>
584
- </View>
585
-
586
- {/* Mode Selector */}
587
- <ModeSelector
588
- modes={availableModes}
589
- activeMode={mode}
590
- onSelect={(m) => onModeChange?.(m)}
591
- isArabic={isArabic}
592
- totalUnread={totalUnread}
593
- />
594
-
595
- {/* Result Bubble — only show in text/voice modes, NOT in human mode */}
596
- {lastResult && mode !== 'human' && (() => {
597
- const cleanMessage = lastResult.message.trim();
598
- return (
599
- <View style={[
600
- styles.resultBubble,
601
- lastResult.success
602
- ? [styles.resultSuccess, theme?.successColor ? { backgroundColor: theme.successColor } : undefined]
603
- : [styles.resultError, theme?.errorColor ? { backgroundColor: theme.errorColor } : undefined],
604
- ]}>
605
- <ScrollView style={styles.resultScroll} nestedScrollEnabled>
606
- <Text style={[styles.resultText, { textAlign: isArabic ? 'right' : 'left' }, theme?.textColor ? { color: theme.textColor } : undefined]}>
607
- {lastUserMessage ?? cleanMessage}
608
- </Text>
609
- </ScrollView>
610
- {onDismiss && (
611
- <Pressable style={styles.dismissButton} onPress={onDismiss} hitSlop={12}>
612
- <CloseIcon size={14} color={theme?.textColor ? theme.textColor : 'rgba(255, 255, 255, 0.6)'} />
613
- </Pressable>
614
- )}
615
- </View>
616
- );
617
- })()}
618
-
619
- {/* Mode-specific input */}
620
- {mode === 'text' && (
621
- <TextInputRow
622
- text={text}
623
- setText={setText}
624
- onSend={handleSend}
625
- isThinking={isThinking}
626
- isArabic={isArabic}
627
- theme={theme}
628
- />
629
- )}
630
-
631
- {/* Human mode: ticket list or chat */}
632
- {mode === 'human' && !selectedTicketId && (
633
- <ScrollView style={styles.ticketList} nestedScrollEnabled>
634
- {tickets.length === 0 ? (
635
- <Text style={styles.emptyText}>No active tickets</Text>
636
- ) : (
637
- tickets.map(ticket => {
638
- const unreadCount = unreadCounts[ticket.id] || 0;
639
- return (
640
- <Pressable
641
- key={ticket.id}
642
- style={styles.ticketCard}
643
- onPress={() => onTicketSelect?.(ticket.id)}
644
- >
645
- <View style={styles.ticketTopRow}>
646
- <Text style={styles.ticketReason} numberOfLines={2}>
647
- {ticket.history.length > 0 ? (ticket.history[ticket.history.length - 1]?.content ?? ticket.reason) : ticket.reason}
648
- </Text>
649
- {unreadCount > 0 && (
650
- <View style={styles.unreadBadge}>
651
- <Text style={styles.unreadBadgeText}>
652
- {unreadCount > 99 ? '99+' : unreadCount}
653
- </Text>
654
- </View>
655
- )}
656
- </View>
657
- <View style={styles.ticketMeta}>
658
- <Text style={[styles.ticketStatus, ticket.status === 'open' && styles.statusOpen]}>
659
- {ticket.status}
660
- </Text>
661
- </View>
662
- </Pressable>
663
- );
664
- })
665
- )}
666
- </ScrollView>
667
- )}
668
-
669
- {mode === 'human' && selectedTicketId && null}
670
-
671
- {mode === 'voice' && (
672
- <VoiceControlsRow
673
- isMicActive={isMicActive}
674
- isSpeakerMuted={isSpeakerMuted}
675
- onMicToggle={onMicToggle || (() => {})}
676
- onSpeakerToggle={onSpeakerToggle || (() => {})}
677
- isAISpeaking={isAISpeaking}
678
- isVoiceConnected={isVoiceConnected}
679
- isArabic={isArabic}
680
- onStopSession={onStopSession}
681
- />
682
- )}
683
-
684
- {/* Voice controls removed since mode handles it */}
685
-
686
- </Animated.View>
687
- );
688
- }
689
-
690
- // ─── Styles ────────────────────────────────────────────────────
691
-
692
- const styles = StyleSheet.create({
693
- fabContainer: {
694
- position: 'absolute',
695
- zIndex: 9999,
696
- },
697
- fab: {
698
- width: 60,
699
- height: 60,
700
- borderRadius: 30,
701
- backgroundColor: '#1a1a2e',
702
- justifyContent: 'center',
703
- alignItems: 'center',
704
- elevation: 5,
705
- shadowColor: '#000',
706
- shadowOffset: { width: 0, height: 4 },
707
- shadowOpacity: 0.3,
708
- shadowRadius: 6,
709
- },
710
-
711
- fabIcon: {
712
- fontSize: 28,
713
- },
714
- expandedContainer: {
715
- position: 'absolute',
716
- zIndex: 9999,
717
- width: 340,
718
- backgroundColor: 'rgba(26, 26, 46, 0.95)',
719
- borderRadius: 24,
720
- padding: 16,
721
- paddingTop: 8,
722
- elevation: 8,
723
- shadowColor: '#000',
724
- shadowOffset: { width: 0, height: 8 },
725
- shadowOpacity: 0.4,
726
- shadowRadius: 10,
727
- },
728
- dragHandleArea: {
729
- width: '100%',
730
- height: 30,
731
- justifyContent: 'center',
732
- alignItems: 'center',
733
- marginBottom: 8,
734
- },
735
- dragGrip: {
736
- width: 40,
737
- height: 5,
738
- backgroundColor: 'rgba(255, 255, 255, 0.3)',
739
- borderRadius: 4,
740
- },
741
- minimizeBtn: {
742
- position: 'absolute',
743
- right: 0,
744
- top: 0,
745
- padding: 8,
746
- },
747
- minimizeText: {
748
- color: '#fff',
749
- fontSize: 18,
750
- fontWeight: 'bold',
751
- },
752
- resultBubble: {
753
- borderRadius: 12,
754
- padding: 12,
755
- marginBottom: 12,
756
- flexDirection: 'row',
757
- alignItems: 'flex-start',
758
- },
759
- resultSuccess: {
760
- backgroundColor: 'rgba(40, 167, 69, 0.2)',
761
- },
762
- resultError: {
763
- backgroundColor: 'rgba(220, 53, 69, 0.2)',
764
- },
765
- resultText: {
766
- color: '#fff',
767
- fontSize: 14,
768
- lineHeight: 20,
769
- flex: 1,
770
- },
771
-
772
- resultScroll: {
773
- maxHeight: 200,
774
- flex: 1,
775
- },
776
- dismissButton: {
777
- marginLeft: 8,
778
- padding: 2,
779
- },
780
- dismissText: {
781
- color: 'rgba(255, 255, 255, 0.6)',
782
- fontSize: 14,
783
- fontWeight: 'bold',
784
- },
785
- inputRow: {
786
- flexDirection: 'row',
787
- alignItems: 'center',
788
- gap: 8,
789
- justifyContent: 'center',
790
- },
791
- input: {
792
- flex: 1,
793
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
794
- borderRadius: 20,
795
- paddingHorizontal: 16,
796
- paddingVertical: 10,
797
- color: '#fff',
798
- fontSize: 16,
799
- },
800
- inputRTL: {
801
- textAlign: 'right',
802
- writingDirection: 'rtl',
803
- },
804
- sendButton: {
805
- width: 40,
806
- height: 40,
807
- borderRadius: 20,
808
- backgroundColor: 'rgba(255, 255, 255, 0.15)',
809
- justifyContent: 'center',
810
- alignItems: 'center',
811
- },
812
- sendButtonDisabled: {
813
- opacity: 0.5,
814
- },
815
- sendButtonText: {
816
- fontSize: 18,
817
- },
818
- dictationButton: {
819
- width: 40,
820
- height: 40,
821
- borderRadius: 20,
822
- backgroundColor: 'rgba(255, 255, 255, 0.15)',
823
- justifyContent: 'center' as const,
824
- alignItems: 'center' as const,
825
- },
826
- dictationButtonActive: {
827
- backgroundColor: 'rgba(255, 59, 48, 0.3)',
828
- },
829
- ticketList: {
830
- maxHeight: 260,
831
- paddingHorizontal: 12,
832
- },
833
- ticketCard: {
834
- backgroundColor: 'rgba(255, 255, 255, 0.08)',
835
- borderRadius: 12,
836
- padding: 14,
837
- marginBottom: 8,
838
- },
839
- ticketTopRow: {
840
- flexDirection: 'row',
841
- alignItems: 'flex-start',
842
- justifyContent: 'space-between',
843
- gap: 8,
844
- },
845
- ticketReason: {
846
- color: '#fff',
847
- fontSize: 14,
848
- fontWeight: '600',
849
- flex: 1,
850
- },
851
- ticketMeta: {
852
- flexDirection: 'row',
853
- justifyContent: 'space-between',
854
- alignItems: 'center',
855
- marginTop: 6,
856
- },
857
-
858
- ticketStatus: {
859
- fontSize: 11,
860
- fontWeight: '600',
861
- color: 'rgba(255, 255, 255, 0.5)',
862
- },
863
- statusOpen: {
864
- color: '#FF9500',
865
- },
866
- emptyText: {
867
- color: 'rgba(255, 255, 255, 0.4)',
868
- fontSize: 14,
869
- textAlign: 'center',
870
- paddingVertical: 30,
871
- },
872
- backBtn: {
873
- paddingHorizontal: 16,
874
- paddingVertical: 10,
875
- },
876
- backBtnText: {
877
- color: '#7B68EE',
878
- fontSize: 14,
879
- fontWeight: '600',
880
- },
881
- chatMessages: {
882
- maxHeight: 200,
883
- paddingHorizontal: 12,
884
- marginBottom: 8,
885
- },
886
- msgBubble: {
887
- borderRadius: 12,
888
- padding: 10,
889
- marginBottom: 6,
890
- maxWidth: '85%',
891
- },
892
- msgBubbleUser: {
893
- backgroundColor: 'rgba(123, 104, 238, 0.3)',
894
- alignSelf: 'flex-end',
895
- },
896
- msgBubbleAgent: {
897
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
898
- alignSelf: 'flex-start',
899
- },
900
- msgText: {
901
- color: '#fff',
902
- fontSize: 13,
903
- lineHeight: 18,
904
- },
905
- typingIndicator: {
906
- paddingHorizontal: 16,
907
- paddingBottom: 6,
908
- },
909
- typingText: {
910
- fontSize: 12,
911
- color: 'rgba(255, 255, 255, 0.4)',
912
- fontStyle: 'italic',
913
- },
914
- unreadBadge: {
915
- minWidth: 16,
916
- height: 16,
917
- borderRadius: 8,
918
- backgroundColor: '#FF3B30',
919
- justifyContent: 'center',
920
- alignItems: 'center',
921
- paddingHorizontal: 4,
922
- },
923
- unreadBadgeText: {
924
- color: '#fff',
925
- fontSize: 10,
926
- fontWeight: '700',
927
- lineHeight: 16,
928
- textAlign: 'center',
929
- },
930
- humanTabBadge: {
931
- minWidth: 14,
932
- height: 14,
933
- borderRadius: 7,
934
- backgroundColor: '#FF3B30',
935
- justifyContent: 'center',
936
- alignItems: 'center',
937
- paddingHorizontal: 3,
938
- marginLeft: 3,
939
- },
940
- humanTabBadgeText: {
941
- color: '#fff',
942
- fontSize: 8,
943
- fontWeight: '700',
944
- lineHeight: 14,
945
- textAlign: 'center',
946
- },
947
- fabUnreadBadge: {
948
- position: 'absolute',
949
- top: -2,
950
- right: -2,
951
- minWidth: 20,
952
- height: 20,
953
- borderRadius: 10,
954
- backgroundColor: '#FF3B30',
955
- justifyContent: 'center',
956
- alignItems: 'center',
957
- paddingHorizontal: 5,
958
- borderWidth: 2,
959
- borderColor: '#1a1a2e',
960
- },
961
- fabUnreadBadgeText: {
962
- color: '#fff',
963
- fontSize: 11,
964
- fontWeight: '700',
965
- lineHeight: 20,
966
- textAlign: 'center',
967
- },
968
- });
969
-
970
- const modeStyles = StyleSheet.create({
971
- container: {
972
- flexDirection: 'row',
973
- marginBottom: 12,
974
- borderRadius: 12,
975
- backgroundColor: 'rgba(255, 255, 255, 0.08)',
976
- padding: 3,
977
- },
978
- tab: {
979
- flex: 1,
980
- flexDirection: 'row',
981
- alignItems: 'center',
982
- justifyContent: 'center',
983
- paddingVertical: 8,
984
- borderRadius: 10,
985
- gap: 4,
986
- },
987
- tabActive: {
988
- backgroundColor: 'rgba(255, 255, 255, 0.15)',
989
- },
990
- tabIcon: {
991
- fontSize: 14,
992
- },
993
- tabLabel: {
994
- color: 'rgba(255, 255, 255, 0.5)',
995
- fontSize: 12,
996
- fontWeight: '600',
997
- },
998
- tabLabelActive: {
999
- color: '#fff',
1000
- },
1001
- });
1002
-
1003
- const audioStyles = StyleSheet.create({
1004
- controlBtn: {
1005
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
1006
- justifyContent: 'center',
1007
- alignItems: 'center',
1008
- },
1009
- controlBtnActive: {
1010
- backgroundColor: 'rgba(255, 100, 100, 0.2)',
1011
- },
1012
- controlIcon: {
1013
- fontSize: 16,
1014
- },
1015
- micButton: {
1016
- flex: 1,
1017
- flexDirection: 'row',
1018
- alignItems: 'center',
1019
- justifyContent: 'center',
1020
- paddingVertical: 12,
1021
- borderRadius: 24,
1022
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
1023
- gap: 8,
1024
- },
1025
- micButtonActive: {
1026
- backgroundColor: 'rgba(255, 59, 48, 0.3)',
1027
- },
1028
- micButtonSpeaking: {
1029
- backgroundColor: 'rgba(52, 199, 89, 0.3)',
1030
- },
1031
- micIconWrap: {
1032
- width: 20,
1033
- height: 20,
1034
- alignItems: 'center' as const,
1035
- justifyContent: 'center' as const,
1036
- },
1037
- micLabel: {
1038
- color: '#fff',
1039
- fontSize: 14,
1040
- fontWeight: '600',
1041
- },
1042
-
1043
- micButtonConnecting: {
1044
- backgroundColor: 'rgba(255, 200, 50, 0.2)',
1045
- opacity: 0.7,
1046
- },
1047
- statusDot: {
1048
- width: 10,
1049
- height: 10,
1050
- borderRadius: 5,
1051
- },
1052
- statusDotConnected: {
1053
- backgroundColor: '#34C759',
1054
- },
1055
- statusDotConnecting: {
1056
- backgroundColor: '#FFCC00',
1057
- },
1058
- humanStatusRow: {
1059
- flexDirection: 'row',
1060
- alignItems: 'center',
1061
- justifyContent: 'center',
1062
- paddingVertical: 10,
1063
- gap: 8,
1064
- },
1065
- humanStatusDot: {
1066
- width: 8,
1067
- height: 8,
1068
- borderRadius: 4,
1069
- backgroundColor: '#FF9500',
1070
- },
1071
- humanStatusText: {
1072
- color: '#FF9500',
1073
- fontSize: 13,
1074
- fontWeight: '600',
1075
- },
1076
- ticketList: {
1077
- maxHeight: 260,
1078
- paddingHorizontal: 12,
1079
- },
1080
- ticketCard: {
1081
- backgroundColor: 'rgba(255, 255, 255, 0.08)',
1082
- borderRadius: 12,
1083
- padding: 14,
1084
- marginBottom: 8,
1085
- },
1086
- ticketReason: {
1087
- color: '#fff',
1088
- fontSize: 14,
1089
- fontWeight: '600',
1090
- },
1091
- ticketMeta: {
1092
- flexDirection: 'row',
1093
- justifyContent: 'space-between',
1094
- alignItems: 'center',
1095
- marginTop: 6,
1096
- },
1097
-
1098
- ticketStatus: {
1099
- fontSize: 11,
1100
- fontWeight: '600',
1101
- color: 'rgba(255, 255, 255, 0.5)',
1102
- },
1103
- statusOpen: {
1104
- color: '#FF9500',
1105
- },
1106
- emptyText: {
1107
- color: 'rgba(255, 255, 255, 0.4)',
1108
- fontSize: 14,
1109
- textAlign: 'center',
1110
- paddingVertical: 30,
1111
- },
1112
- backBtn: {
1113
- paddingHorizontal: 16,
1114
- paddingVertical: 10,
1115
- },
1116
- backBtnText: {
1117
- color: '#7B68EE',
1118
- fontSize: 14,
1119
- fontWeight: '600',
1120
- },
1121
- backRow: {
1122
- flexDirection: 'row',
1123
- alignItems: 'center',
1124
- paddingHorizontal: 12,
1125
- paddingVertical: 8,
1126
- borderBottomWidth: 1,
1127
- borderBottomColor: 'rgba(255, 255, 255, 0.08)',
1128
- },
1129
- backText: {
1130
- color: '#7B68EE',
1131
- fontSize: 14,
1132
- fontWeight: '600',
1133
- },
1134
- emptyTickets: {
1135
- alignItems: 'center',
1136
- justifyContent: 'center',
1137
- paddingVertical: 40,
1138
- },
1139
- emptyTicketsText: {
1140
- color: 'rgba(255, 255, 255, 0.4)',
1141
- fontSize: 14,
1142
- },
1143
- });