@qafka/react-native 2.0.0

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 (178) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CONTRIBUTING.md +92 -0
  3. package/LICENSE +22 -0
  4. package/README.md +109 -0
  5. package/SECURITY.md +67 -0
  6. package/android/build.gradle +35 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/com/qafka/attestation/QafkaAttestationModule.kt +92 -0
  9. package/android/src/main/java/com/qafka/attestation/QafkaAttestationPackage.kt +22 -0
  10. package/android/src/main/java/com/qafka/audio/QafkaAudioModule.kt +290 -0
  11. package/android/src/main/java/com/qafka/clipboard/QafkaClipboardModule.kt +28 -0
  12. package/android/src/main/java/com/qafka/storage/QafkaStorageModule.kt +80 -0
  13. package/app.plugin.js +1 -0
  14. package/dist/QafkaSDK.d.ts +174 -0
  15. package/dist/QafkaSDK.js +461 -0
  16. package/dist/cards/bindings/resolveFieldName.d.ts +25 -0
  17. package/dist/cards/bindings/resolveFieldName.js +82 -0
  18. package/dist/cards/cta/CardContext.d.ts +16 -0
  19. package/dist/cards/cta/CardContext.js +58 -0
  20. package/dist/cards/cta/dispatcher.d.ts +7 -0
  21. package/dist/cards/cta/dispatcher.js +90 -0
  22. package/dist/cards/cta/types.d.ts +66 -0
  23. package/dist/cards/cta/types.js +2 -0
  24. package/dist/cards/index.d.ts +20 -0
  25. package/dist/cards/index.js +34 -0
  26. package/dist/cards/primitives/QButton.d.ts +10 -0
  27. package/dist/cards/primitives/QButton.js +115 -0
  28. package/dist/cards/primitives/QDivider.d.ts +7 -0
  29. package/dist/cards/primitives/QDivider.js +17 -0
  30. package/dist/cards/primitives/QIcon.d.ts +13 -0
  31. package/dist/cards/primitives/QIcon.js +26 -0
  32. package/dist/cards/primitives/QImage.d.ts +9 -0
  33. package/dist/cards/primitives/QImage.js +22 -0
  34. package/dist/cards/primitives/QText.d.ts +9 -0
  35. package/dist/cards/primitives/QText.js +30 -0
  36. package/dist/cards/primitives/QView.d.ts +8 -0
  37. package/dist/cards/primitives/QView.js +19 -0
  38. package/dist/cards/renderer/CardRenderer.d.ts +19 -0
  39. package/dist/cards/renderer/CardRenderer.js +64 -0
  40. package/dist/cards/renderer/renderNode.d.ts +13 -0
  41. package/dist/cards/renderer/renderNode.js +42 -0
  42. package/dist/cards/types.d.ts +110 -0
  43. package/dist/cards/types.js +6 -0
  44. package/dist/components/ActionResultBadge.d.ts +12 -0
  45. package/dist/components/ActionResultBadge.js +58 -0
  46. package/dist/components/ChatPage.d.ts +44 -0
  47. package/dist/components/ChatPage.js +84 -0
  48. package/dist/components/DataChip.d.ts +8 -0
  49. package/dist/components/DataChip.js +80 -0
  50. package/dist/components/DataChipList.d.ts +13 -0
  51. package/dist/components/DataChipList.js +21 -0
  52. package/dist/components/FloatingButton.d.ts +11 -0
  53. package/dist/components/FloatingButton.js +162 -0
  54. package/dist/components/InputArea.d.ts +57 -0
  55. package/dist/components/InputArea.js +142 -0
  56. package/dist/components/MarkdownText.d.ts +15 -0
  57. package/dist/components/MarkdownText.js +283 -0
  58. package/dist/components/MessageBubble.d.ts +134 -0
  59. package/dist/components/MessageBubble.js +384 -0
  60. package/dist/components/NavigationSuggestion.d.ts +11 -0
  61. package/dist/components/NavigationSuggestion.js +109 -0
  62. package/dist/components/Qafka.d.ts +39 -0
  63. package/dist/components/Qafka.handlers.d.ts +21 -0
  64. package/dist/components/Qafka.handlers.js +54 -0
  65. package/dist/components/Qafka.js +493 -0
  66. package/dist/components/Qafka.styles.d.ts +19 -0
  67. package/dist/components/Qafka.styles.js +101 -0
  68. package/dist/components/Qafka.types.d.ts +744 -0
  69. package/dist/components/Qafka.types.js +2 -0
  70. package/dist/components/Qafka.utils.d.ts +7 -0
  71. package/dist/components/Qafka.utils.js +34 -0
  72. package/dist/components/QafkaProvider.d.ts +12 -0
  73. package/dist/components/QafkaProvider.js +87 -0
  74. package/dist/components/QuickReplies.d.ts +14 -0
  75. package/dist/components/QuickReplies.js +48 -0
  76. package/dist/components/StepProgressIndicator.d.ts +12 -0
  77. package/dist/components/StepProgressIndicator.js +48 -0
  78. package/dist/components/SuggestionButton.d.ts +42 -0
  79. package/dist/components/SuggestionButton.js +67 -0
  80. package/dist/components/ToolStatusPill.d.ts +20 -0
  81. package/dist/components/ToolStatusPill.js +43 -0
  82. package/dist/components/TypingIndicator.d.ts +28 -0
  83. package/dist/components/TypingIndicator.js +109 -0
  84. package/dist/components/VoicePage.d.ts +48 -0
  85. package/dist/components/VoicePage.js +683 -0
  86. package/dist/components/defaults/DefaultCard.d.ts +14 -0
  87. package/dist/components/defaults/DefaultCard.js +156 -0
  88. package/dist/components/defaults/DefaultDetail.d.ts +14 -0
  89. package/dist/components/defaults/DefaultDetail.js +138 -0
  90. package/dist/components/defaults/DefaultList.d.ts +12 -0
  91. package/dist/components/defaults/DefaultList.js +98 -0
  92. package/dist/components/defaults/DefaultTable.d.ts +14 -0
  93. package/dist/components/defaults/DefaultTable.js +204 -0
  94. package/dist/components/defaults/index.d.ts +14 -0
  95. package/dist/components/defaults/index.js +25 -0
  96. package/dist/components/index.d.ts +22 -0
  97. package/dist/components/index.js +36 -0
  98. package/dist/constants.d.ts +10 -0
  99. package/dist/constants.js +13 -0
  100. package/dist/hooks/useChatMessages.d.ts +72 -0
  101. package/dist/hooks/useChatMessages.js +505 -0
  102. package/dist/hooks/useContextManager.d.ts +12 -0
  103. package/dist/hooks/useContextManager.js +46 -0
  104. package/dist/hooks/useProjectTheme.d.ts +19 -0
  105. package/dist/hooks/useProjectTheme.js +163 -0
  106. package/dist/hooks/useSDK.d.ts +31 -0
  107. package/dist/hooks/useSDK.js +103 -0
  108. package/dist/hooks/useVoiceChat.d.ts +110 -0
  109. package/dist/hooks/useVoiceChat.js +436 -0
  110. package/dist/index.d.ts +13 -0
  111. package/dist/index.js +59 -0
  112. package/dist/native/QafkaAttestation.d.ts +23 -0
  113. package/dist/native/QafkaAttestation.js +70 -0
  114. package/dist/native/QafkaAudio.d.ts +14 -0
  115. package/dist/native/QafkaAudio.js +31 -0
  116. package/dist/native/QafkaClipboard.d.ts +11 -0
  117. package/dist/native/QafkaClipboard.js +14 -0
  118. package/dist/native/QafkaStorage.d.ts +15 -0
  119. package/dist/native/QafkaStorage.js +12 -0
  120. package/dist/resolve-project-config.d.ts +35 -0
  121. package/dist/resolve-project-config.js +41 -0
  122. package/dist/runtime-config-loader.d.ts +37 -0
  123. package/dist/runtime-config-loader.js +53 -0
  124. package/dist/services/AttestationManager.d.ts +38 -0
  125. package/dist/services/AttestationManager.js +296 -0
  126. package/dist/services/BackendService.d.ts +156 -0
  127. package/dist/services/BackendService.js +755 -0
  128. package/dist/services/ConversationManager.d.ts +43 -0
  129. package/dist/services/ConversationManager.js +96 -0
  130. package/dist/services/NavigationHandler.d.ts +29 -0
  131. package/dist/services/NavigationHandler.js +70 -0
  132. package/dist/services/RealtimeService.d.ts +83 -0
  133. package/dist/services/RealtimeService.js +203 -0
  134. package/dist/services/storage.d.ts +11 -0
  135. package/dist/services/storage.js +15 -0
  136. package/dist/services/storageCore.d.ts +17 -0
  137. package/dist/services/storageCore.js +46 -0
  138. package/dist/themes/dark.d.ts +5 -0
  139. package/dist/themes/dark.js +129 -0
  140. package/dist/themes/index.d.ts +12 -0
  141. package/dist/themes/index.js +33 -0
  142. package/dist/themes/light.d.ts +5 -0
  143. package/dist/themes/light.js +129 -0
  144. package/dist/themes/types.d.ts +155 -0
  145. package/dist/themes/types.js +5 -0
  146. package/dist/types/chat.d.ts +126 -0
  147. package/dist/types/chat.js +5 -0
  148. package/dist/types/components.d.ts +56 -0
  149. package/dist/types/components.js +16 -0
  150. package/dist/types/external-navigation.d.ts +19 -0
  151. package/dist/types/external-navigation.js +8 -0
  152. package/dist/types/index.d.ts +9 -0
  153. package/dist/types/index.js +25 -0
  154. package/dist/types/navigation.d.ts +86 -0
  155. package/dist/types/navigation.js +5 -0
  156. package/dist/types/sdk.d.ts +36 -0
  157. package/dist/types/sdk.js +5 -0
  158. package/dist/utils/deepMerge.d.ts +46 -0
  159. package/dist/utils/deepMerge.js +70 -0
  160. package/dist/utils/fontUtils.d.ts +8 -0
  161. package/dist/utils/fontUtils.js +16 -0
  162. package/dist/validate-end-user.d.ts +18 -0
  163. package/dist/validate-end-user.js +74 -0
  164. package/expo-plugin/withQafkaAttestation.js +57 -0
  165. package/ios/QafkaAttestation.m +25 -0
  166. package/ios/QafkaAttestation.swift +128 -0
  167. package/ios/QafkaAudio.m +23 -0
  168. package/ios/QafkaAudio.swift +519 -0
  169. package/ios/QafkaClipboard.m +10 -0
  170. package/ios/QafkaClipboard.swift +21 -0
  171. package/ios/QafkaReactImports.h +2 -0
  172. package/ios/QafkaStorage.m +26 -0
  173. package/ios/QafkaStorage.swift +118 -0
  174. package/package.json +82 -0
  175. package/qafka.config.d.ts +9 -0
  176. package/qafka.config.js +9 -0
  177. package/react-native-qafka.podspec +28 -0
  178. package/react-native.config.js +14 -0
@@ -0,0 +1,493 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Qafka = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const defaults_1 = require("./defaults");
40
+ const useSDK_1 = require("../hooks/useSDK");
41
+ const useContextManager_1 = require("../hooks/useContextManager");
42
+ const useChatMessages_1 = require("../hooks/useChatMessages");
43
+ const useProjectTheme_1 = require("../hooks/useProjectTheme");
44
+ const Qafka_styles_1 = require("./Qafka.styles");
45
+ const Qafka_handlers_1 = require("./Qafka.handlers");
46
+ const react_native_safe_area_context_1 = require("react-native-safe-area-context");
47
+ const QafkaSDK_1 = require("../QafkaSDK");
48
+ const validate_end_user_1 = require("../validate-end-user");
49
+ const ChatPage_1 = require("./ChatPage");
50
+ const VoicePage_1 = require("./VoicePage");
51
+ const useVoiceChat_1 = require("../hooks/useVoiceChat");
52
+ const { width: SCREEN_WIDTH } = react_native_1.Dimensions.get('window');
53
+ /**
54
+ * Qafka Component
55
+ *
56
+ * Main chat widget component with full UI and functionality.
57
+ *
58
+ * Features:
59
+ * - Three display modes: fullscreen, inline, floating
60
+ * - Complete chat UI with message list and input
61
+ * - Integration with QafkaSDK for backend communication
62
+ * - Auto-loading theme from API
63
+ * - Theme support (light/dark) with overrides
64
+ * - Typing indicators and streaming responses
65
+ * - Tool Registry integration
66
+ * - Message metadata display
67
+ * - Voice chat mode (swipe to access)
68
+ *
69
+ * @example Basic usage
70
+ * ```tsx
71
+ * <Qafka
72
+ * mode="fullscreen"
73
+ * theme="light"
74
+ * />
75
+ * ```
76
+ *
77
+ * @example With theme override
78
+ * ```tsx
79
+ * <Qafka
80
+ * themeOverride={{
81
+ * colors: {
82
+ * userBubble: '#FF0000',
83
+ * userBubbleText: '#FFFFFF'
84
+ * }
85
+ * }}
86
+ * />
87
+ * ```
88
+ */
89
+ exports.Qafka = (0, react_1.forwardRef)(function Qafka({ style, apiUrl: apiUrlProp, subProjectId, projectId, locale, mode = 'fullscreen', theme: themeName = 'light', customTheme, themeOverride, isAuthenticated, endUserId, endUserData, enableStreaming = true, voiceEnabled: voiceEnabledProp = true, context: userContext, contextDescription, components: customComponents, showTimestamps = true, placeholder = 'Type a message...', maxMessageLength = 500, greetingMessage, onReady, onMessageSent, onResponseReceived, onError, onNavigationSuggest, onNavigationAction, onExternalSuggestion, onCardDeepLink, onCardSuggestMessage, onCardExternalNavigation, onCardShare, onCardCopy, onCardToolTrigger, onCardCTAClick, onToolSuggested, onToolDataRequested, onActionResult, onStepCompleted, onFileUploadRequest, onExtractionResult, onClose, onBack, CloseComponent, BackComponent, navigationLabelFormat, NavigationButtonComponent, voiceComponents, voiceTranscript = 'centered', toolRenderMode, }, ref) {
90
+ // === ALL HOOKS FIRST (Rules of Hooks compliance) ===
91
+ const insets = (0, react_native_safe_area_context_1.useSafeAreaInsets)();
92
+ const { sdkReady, error: sdkError, resolvedApiKey, resolvedApiUrl } = (0, useSDK_1.useSDK)({
93
+ apiUrl: apiUrlProp,
94
+ subProjectId,
95
+ projectId,
96
+ locale,
97
+ onReady,
98
+ onError,
99
+ });
100
+ const { theme, isLoadingTheme, greeting: apiGreeting, initialMessage: initialMessageText, isInitialMessageEnabled, voiceEnabled, } = (0, useProjectTheme_1.useProjectTheme)({
101
+ sdkReady,
102
+ themeName,
103
+ customTheme,
104
+ themeOverride,
105
+ subProjectId,
106
+ });
107
+ const isVoiceAvailable = voiceEnabled && voiceEnabledProp !== false;
108
+ const componentRegistry = {
109
+ ...defaults_1.defaultComponentRegistry,
110
+ ...customComponents,
111
+ };
112
+ const enrichedContext = (0, react_1.useMemo)(() => {
113
+ if (isAuthenticated === undefined)
114
+ return userContext;
115
+ return { ...userContext, isAuthenticated };
116
+ }, [userContext, isAuthenticated]);
117
+ // Validate at mount and on every prop change. Throwing here bubbles
118
+ // into the host app's error boundary the same way useSDK throws for
119
+ // missing project config; the message is actionable enough for the
120
+ // developer to fix the placeholder without inspecting our internals.
121
+ const validatedEndUserId = (0, react_1.useMemo)(() => (0, validate_end_user_1.validateEndUserId)(endUserId), [endUserId]);
122
+ const validatedEndUserData = (0, react_1.useMemo)(() => (0, validate_end_user_1.validateEndUserData)(endUserData), [endUserData]);
123
+ const { currentContext } = (0, useContextManager_1.useContextManager)({
124
+ initialContext: enrichedContext,
125
+ debounceMs: 500,
126
+ });
127
+ const voiceChat = (0, useVoiceChat_1.useVoiceChat)({
128
+ // Voice WebSocket still uses apiKey-based auth. In dev, the resolved
129
+ // developmentKey from qafka.config.js flows through. In production, this
130
+ // is null and voice will fail at WS handshake.
131
+ apiUrl: resolvedApiUrl || apiUrlProp || '',
132
+ apiKey: resolvedApiKey || '',
133
+ userContext: currentContext,
134
+ contextDescription,
135
+ onToolSuggested,
136
+ toolRenderMode,
137
+ // Forward the device-attestation session token so the realtime WS
138
+ // matches the auth posture of HTTP requests. Without this, the server
139
+ // rejects voice connections in production with `SESSION_TOKEN_REQUIRED`.
140
+ getSessionToken: () => QafkaSDK_1.QafkaSDK.getInstance().getSessionToken(),
141
+ // Text/voice parity: the realtime gateway expects the same end-user
142
+ // identity the HTTP chat path sends, so the row it creates can be
143
+ // grouped with text-mode rows for the same user.
144
+ endUserId: validatedEndUserId,
145
+ endUserData: validatedEndUserData,
146
+ });
147
+ const { toolStatus: voiceToolStatus, renderedTools: voiceRenderedTools, transcriptOverrideForTurn, setToolStatusManually, clearRenderedTools, connect: voiceConnect, disconnect: voiceDisconnect, pauseMic: voicePauseMic, resumeMic: voiceResumeMic, isMuted: voiceIsMuted, mute: voiceMute, unmute: voiceUnmute, toggleMute: voiceToggleMute, } = voiceChat;
148
+ const { messages, isTyping, isSending, streamingMessage, toolStatus, flatListRef, handleSend, setMessages, setIsTyping, setStreamingMessage, } = (0, useChatMessages_1.useChatMessages)({
149
+ enableStreaming,
150
+ context: currentContext,
151
+ contextDescription,
152
+ onMessageSent,
153
+ onResponseReceived,
154
+ onError,
155
+ onNavigationSuggest,
156
+ onToolSuggested,
157
+ onActionResult,
158
+ onStepCompleted,
159
+ onFileUploadRequest,
160
+ onExtractionResult,
161
+ navigationLabelFormat,
162
+ });
163
+ (0, react_1.useImperativeHandle)(ref, () => ({
164
+ setLoading(visible, message) {
165
+ setToolStatusManually(visible
166
+ ? { toolKey: 'manual', loadingMessage: message ?? 'Processing…' }
167
+ : null);
168
+ },
169
+ clearRenderedTools() {
170
+ clearRenderedTools();
171
+ },
172
+ sendMessage(text) {
173
+ // Fire-and-forget; consumer doesn't need to await the streaming reply.
174
+ void handleSend(text);
175
+ },
176
+ async connectVoice() {
177
+ await voiceConnect();
178
+ },
179
+ async disconnectVoice() {
180
+ await voiceDisconnect();
181
+ },
182
+ async pauseMic() {
183
+ await voicePauseMic();
184
+ },
185
+ async resumeMic() {
186
+ await voiceResumeMic();
187
+ },
188
+ mute() {
189
+ voiceMute();
190
+ },
191
+ unmute() {
192
+ voiceUnmute();
193
+ },
194
+ toggleMute() {
195
+ voiceToggleMute();
196
+ },
197
+ get isMuted() {
198
+ return voiceIsMuted;
199
+ },
200
+ }), [
201
+ setToolStatusManually,
202
+ clearRenderedTools,
203
+ handleSend,
204
+ voiceConnect,
205
+ voiceDisconnect,
206
+ voicePauseMic,
207
+ voiceResumeMic,
208
+ voiceMute,
209
+ voiceUnmute,
210
+ voiceToggleMute,
211
+ voiceIsMuted,
212
+ ]);
213
+ const [isInitialMessageLoading, setIsInitialMessageLoading] = (0, react_1.useState)(false);
214
+ const [keyboardHeight, setKeyboardHeight] = (0, react_1.useState)(0);
215
+ const scrollViewRef = (0, react_1.useRef)(null);
216
+ // Combine messages with streaming message for FlatList
217
+ const displayMessages = react_1.default.useMemo(() => {
218
+ if (streamingMessage) {
219
+ // Add streaming message as the last item
220
+ const streamingMsg = {
221
+ id: 'streaming-temp',
222
+ role: 'assistant',
223
+ text: streamingMessage,
224
+ content: streamingMessage,
225
+ timestamp: new Date(),
226
+ isStreaming: true, // Flag to identify streaming message
227
+ };
228
+ return [...messages, streamingMsg];
229
+ }
230
+ return messages;
231
+ }, [messages, streamingMessage]);
232
+ // Start a fresh conversation when the widget mounts.
233
+ (0, react_1.useEffect)(() => {
234
+ if (sdkReady) {
235
+ const startNewSession = async () => {
236
+ try {
237
+ const sdk = QafkaSDK_1.QafkaSDK.getInstance();
238
+ await sdk.startNewConversation();
239
+ }
240
+ catch (error) {
241
+ // Silent error handling
242
+ }
243
+ };
244
+ startNewSession();
245
+ }
246
+ }, [sdkReady]);
247
+ // Tool Data Channel: register the partner resolver on the
248
+ // singleton-bound BackendService. Re-registers when the prop identity
249
+ // changes (e.g. when host swaps the closure on auth state change).
250
+ (0, react_1.useEffect)(() => {
251
+ if (!sdkReady)
252
+ return;
253
+ QafkaSDK_1.QafkaSDK.getInstance().setOnToolDataRequested(onToolDataRequested ?? null);
254
+ return () => {
255
+ // Clear on unmount/prop change so a stale closure can't be invoked
256
+ // against the next page's user state.
257
+ QafkaSDK_1.QafkaSDK.getInstance().setOnToolDataRequested(null);
258
+ };
259
+ }, [sdkReady, onToolDataRequested]);
260
+ // Push the validated end-user identity into the SDK singleton whenever
261
+ // the props (or their validated coercions) change. This is distinct
262
+ // from `userContext`: endUserId/endUserData live on a separate lane
263
+ // that never enters the AI prompt.
264
+ (0, react_1.useEffect)(() => {
265
+ if (!sdkReady)
266
+ return;
267
+ QafkaSDK_1.QafkaSDK.getInstance().setEndUser(validatedEndUserId, validatedEndUserData);
268
+ }, [sdkReady, validatedEndUserId, validatedEndUserData]);
269
+ // Trigger initial message when config is loaded and SDK is ready
270
+ (0, react_1.useEffect)(() => {
271
+ if (!sdkReady ||
272
+ !isInitialMessageEnabled ||
273
+ !initialMessageText ||
274
+ isInitialMessageLoading ||
275
+ isLoadingTheme) {
276
+ return;
277
+ }
278
+ const triggerInitialMessage = async () => {
279
+ setIsInitialMessageLoading(true);
280
+ setIsTyping(true);
281
+ try {
282
+ const sdk = QafkaSDK_1.QafkaSDK.getInstance();
283
+ if (enableStreaming) {
284
+ let firstChunkReceived = false;
285
+ await sdk.sendMessageStream(initialMessageText, (chunk) => {
286
+ if (!firstChunkReceived) {
287
+ firstChunkReceived = true;
288
+ }
289
+ setStreamingMessage((prev) => prev + chunk);
290
+ }, (response) => {
291
+ setIsTyping(false);
292
+ setStreamingMessage('');
293
+ const finalMessage = (response.text || '').trim();
294
+ const aiMessage = {
295
+ id: response.id || `initial-${Date.now()}`,
296
+ role: 'assistant',
297
+ text: finalMessage,
298
+ content: finalMessage,
299
+ timestamp: new Date(),
300
+ metadata: {
301
+ isInitialMessage: true,
302
+ },
303
+ };
304
+ setMessages((prev) => [...prev, aiMessage]);
305
+ setIsInitialMessageLoading(false);
306
+ }, (err) => {
307
+ setIsTyping(false);
308
+ setStreamingMessage('');
309
+ setIsInitialMessageLoading(false);
310
+ if (__DEV__) {
311
+ console.warn('[Qafka] Initial message error:', err.message);
312
+ }
313
+ }, currentContext, contextDescription, undefined, // onToolSuggestions
314
+ true);
315
+ }
316
+ else {
317
+ const response = await sdk.sendMessage(initialMessageText, currentContext, contextDescription, true);
318
+ const aiMessage = {
319
+ id: response.id || `initial-${Date.now()}`,
320
+ role: 'assistant',
321
+ text: response.text || '',
322
+ content: response.text || '',
323
+ timestamp: new Date(),
324
+ metadata: {
325
+ isInitialMessage: true,
326
+ },
327
+ };
328
+ setMessages((prev) => [...prev, aiMessage]);
329
+ setIsTyping(false);
330
+ setIsInitialMessageLoading(false);
331
+ }
332
+ }
333
+ catch (error) {
334
+ if (__DEV__) {
335
+ console.warn('[Qafka] Initial message failed:', error);
336
+ }
337
+ setIsTyping(false);
338
+ setIsInitialMessageLoading(false);
339
+ }
340
+ };
341
+ triggerInitialMessage();
342
+ }, [sdkReady, isInitialMessageEnabled, initialMessageText, isLoadingTheme]);
343
+ // Action handler
344
+ const handleActionClick = (0, Qafka_handlers_1.createActionHandler)({
345
+ onNavigationAction,
346
+ setMessages,
347
+ });
348
+ // External suggestion handler: prefer app-supplied callback, otherwise
349
+ // fall back to Linking.openURL (with fallbackUrl) via the default handler.
350
+ const handleExternalSuggestionPress = (0, react_1.useCallback)((suggestion) => {
351
+ if (onExternalSuggestion) {
352
+ onExternalSuggestion(suggestion);
353
+ return;
354
+ }
355
+ // fire-and-forget — default handler swallows errors internally
356
+ (0, Qafka_handlers_1.handleExternalSuggestionDefault)(suggestion);
357
+ }, [onExternalSuggestion]);
358
+ // Keyboard handling for Android
359
+ (0, react_1.useEffect)(() => {
360
+ if (react_native_1.Platform.OS === 'android') {
361
+ const showSubscription = react_native_1.Keyboard.addListener('keyboardDidShow', (e) => {
362
+ setKeyboardHeight(e.endCoordinates.height);
363
+ });
364
+ const hideSubscription = react_native_1.Keyboard.addListener('keyboardDidHide', () => {
365
+ setKeyboardHeight(0);
366
+ });
367
+ return () => {
368
+ showSubscription.remove();
369
+ hideSubscription.remove();
370
+ };
371
+ }
372
+ }, []);
373
+ // Handle horizontal scroll between chat and voice pages
374
+ const handleScroll = (0, react_1.useCallback)((event) => {
375
+ const pageIndex = Math.round(event.nativeEvent.contentOffset.x / SCREEN_WIDTH);
376
+ if (pageIndex === 1) {
377
+ voiceChat.connect();
378
+ }
379
+ else {
380
+ voiceChat.pauseMic();
381
+ }
382
+ }, [voiceChat]);
383
+ // Card CTA host callbacks bundle. Stable reference via useMemo
384
+ // so MessageBubble's CardRuntimeProvider doesn't re-mount on every render.
385
+ // MUST be declared before the conditional returns below — Rules of Hooks.
386
+ const cardHostCallbacks = (0, react_1.useMemo)(() => ({
387
+ onDeepLink: onCardDeepLink,
388
+ onSuggestMessage: onCardSuggestMessage,
389
+ onExternalNavigation: onCardExternalNavigation,
390
+ onShare: onCardShare,
391
+ onCopy: onCardCopy,
392
+ onToolTrigger: onCardToolTrigger,
393
+ }), [
394
+ onCardDeepLink,
395
+ onCardSuggestMessage,
396
+ onCardExternalNavigation,
397
+ onCardShare,
398
+ onCardCopy,
399
+ onCardToolTrigger,
400
+ ]);
401
+ const cardLifecycle = (0, react_1.useMemo)(() => ({ onCTAClick: onCardCTAClick }), [onCardCTAClick]);
402
+ // Styles
403
+ const styles = (0, Qafka_styles_1.getWidgetStyles)(theme);
404
+ // === CONDITIONAL RETURNS (after all hooks) ===
405
+ // No blank-canvas gate here: ChatPage renders even before theme/sdkReady,
406
+ // showing the cached greeting (if any) with a TypingIndicator gate and a
407
+ // hidden input until the SDK is ready to accept messages.
408
+ // Shared ChatPage props
409
+ const chatPageProps = {
410
+ messages,
411
+ displayMessages,
412
+ isTyping,
413
+ isSending,
414
+ streamingMessage,
415
+ toolStatus,
416
+ sdkReady,
417
+ sdkError,
418
+ theme,
419
+ handleSend,
420
+ handleActionClick,
421
+ handleExternalSuggestionPress,
422
+ flatListRef,
423
+ placeholder,
424
+ maxMessageLength,
425
+ showTimestamps,
426
+ greetingMessage,
427
+ apiGreeting,
428
+ isInitialMessageEnabled,
429
+ initialMessageText,
430
+ componentRegistry,
431
+ onBack,
432
+ onClose,
433
+ BackComponent,
434
+ CloseComponent,
435
+ NavigationButtonComponent,
436
+ cardHostCallbacks,
437
+ cardLifecycle,
438
+ };
439
+ // The outer container applies safe-area padding (paddingTop:insets.top etc).
440
+ // The horizontal pager ScrollView clips its children, so any negative-margin
441
+ // trick applied INSIDE a page (e.g. VoicePage's Background) can't escape the
442
+ // ScrollView viewport to paint into the notch/home-indicator bands. Fix:
443
+ // wrap the ScrollView itself with negative margins so the ScrollView spans
444
+ // the full screen, then re-apply safe-area padding to the ChatPage slot
445
+ // (which expects to render under nav buttons / above home indicator).
446
+ // VoicePage paints its Background edge-to-edge across its full-screen slot
447
+ // and applies safe-area padding to its inner content directly.
448
+ const outerPaddingTop = insets.top + (react_native_1.Platform.OS === 'android' ? 20 : 0);
449
+ const outerPaddingBottom = react_native_1.Platform.OS === 'android' ? 0 : insets.bottom;
450
+ const chatContent = isVoiceAvailable ? (<react_native_1.View style={{
451
+ flex: 1,
452
+ marginTop: -outerPaddingTop,
453
+ marginBottom: -outerPaddingBottom,
454
+ }}>
455
+ <react_native_1.ScrollView ref={scrollViewRef} horizontal pagingEnabled showsHorizontalScrollIndicator={false} onMomentumScrollEnd={handleScroll} scrollEventThrottle={16} keyboardShouldPersistTaps="handled" style={{ flex: 1 }}>
456
+ {/* Page 0: Chat — re-apply safe-area padding inside this slot. */}
457
+ <react_native_1.View style={{
458
+ width: SCREEN_WIDTH,
459
+ flex: 1,
460
+ paddingTop: outerPaddingTop,
461
+ paddingBottom: outerPaddingBottom,
462
+ }}>
463
+ <ChatPage_1.ChatPage {...chatPageProps}/>
464
+ </react_native_1.View>
465
+
466
+ {/* Page 1: Voice — VoicePage paints Background edge-to-edge and
467
+ handles its own internal safe-area padding for orb/transcript. */}
468
+ <VoicePage_1.VoicePage state={voiceChat.state} transcript={voiceChat.transcript} userTranscript={voiceChat.userTranscript} transcriptHistory={voiceChat.transcriptHistory} transcriptMode={voiceTranscript} amplitude={voiceChat.amplitude} theme={theme} voiceComponents={voiceComponents} toolStatus={voiceToolStatus} renderedTools={voiceRenderedTools} registeredComponents={customComponents} transcriptOverrideForTurn={transcriptOverrideForTurn} DataChipListComponent={voiceComponents?.dataChipList} isMuted={voiceIsMuted} onToggleMute={voiceToggleMute}/>
469
+ </react_native_1.ScrollView>
470
+ </react_native_1.View>) : (<ChatPage_1.ChatPage {...chatPageProps}/>);
471
+ return (<react_native_1.View style={[
472
+ {
473
+ flex: 1,
474
+ paddingTop: outerPaddingTop,
475
+ paddingBottom: outerPaddingBottom,
476
+ backgroundColor: theme.colors.background,
477
+ },
478
+ style,
479
+ ]}>
480
+ {react_native_1.Platform.OS === 'ios' ? (<react_native_1.KeyboardAvoidingView style={styles.containerStyle} behavior="padding" keyboardVerticalOffset={0}>
481
+ {chatContent}
482
+ </react_native_1.KeyboardAvoidingView>) : (<react_native_1.View style={[
483
+ styles.containerStyle,
484
+ {
485
+ paddingBottom: keyboardHeight > 0
486
+ ? keyboardHeight + insets.bottom
487
+ : insets.bottom,
488
+ },
489
+ ]}>
490
+ {chatContent}
491
+ </react_native_1.View>)}
492
+ </react_native_1.View>);
493
+ });
@@ -0,0 +1,19 @@
1
+ import { ViewStyle, TextStyle } from 'react-native';
2
+ import { Theme } from '../themes';
3
+ /**
4
+ * Styles for Qafka component
5
+ */
6
+ export declare const getWidgetStyles: (theme: Theme) => {
7
+ containerStyle: ViewStyle;
8
+ headerStyle: ViewStyle;
9
+ headerTextStyle: TextStyle;
10
+ messageListStyle: ViewStyle;
11
+ emptyStateStyle: ViewStyle;
12
+ emptyStateTextStyle: TextStyle;
13
+ errorStyle: ViewStyle;
14
+ errorTextStyle: TextStyle;
15
+ typingIndicatorContainer: ViewStyle;
16
+ navigationContainer: ViewStyle;
17
+ navButton: ViewStyle;
18
+ navButtonText: TextStyle;
19
+ };
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getWidgetStyles = void 0;
4
+ const fontUtils_1 = require("../utils/fontUtils");
5
+ /**
6
+ * Styles for Qafka component
7
+ */
8
+ const getWidgetStyles = (theme) => {
9
+ const containerStyle = {
10
+ flex: 1,
11
+ backgroundColor: theme.colors.background,
12
+ };
13
+ const headerStyle = {
14
+ paddingHorizontal: theme.spacing.md,
15
+ paddingVertical: theme.spacing.md,
16
+ backgroundColor: theme.colors.surface,
17
+ borderBottomWidth: 1,
18
+ borderBottomColor: theme.colors.border,
19
+ ...theme.shadows.small,
20
+ };
21
+ const headerTextStyle = {
22
+ fontSize: theme.typography.fontSize.lg,
23
+ fontWeight: theme.typography.fontWeight.semibold,
24
+ color: theme.colors.text,
25
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'semibold'),
26
+ };
27
+ const messageListStyle = {
28
+ paddingHorizontal: theme.spacing.md,
29
+ paddingTop: theme.spacing.md,
30
+ paddingBottom: theme.spacing.md,
31
+ };
32
+ const emptyStateStyle = {
33
+ flex: 1,
34
+ alignItems: 'center',
35
+ justifyContent: 'center',
36
+ padding: theme.spacing.xl,
37
+ };
38
+ const emptyStateTextStyle = {
39
+ fontSize: theme.typography.fontSize.md,
40
+ color: theme.colors.textSecondary,
41
+ textAlign: 'center',
42
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'regular'),
43
+ };
44
+ const errorStyle = {
45
+ backgroundColor: theme.colors.error,
46
+ padding: theme.spacing.sm,
47
+ margin: theme.spacing.md,
48
+ borderRadius: theme.borderRadius.md,
49
+ };
50
+ const errorTextStyle = {
51
+ color: theme.colors.textInverse,
52
+ fontSize: theme.typography.fontSize.sm,
53
+ textAlign: 'center',
54
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'regular'),
55
+ };
56
+ const typingIndicatorContainer = {
57
+ paddingHorizontal: theme.spacing.md,
58
+ };
59
+ const navigationContainer = {
60
+ position: 'absolute',
61
+ top: 0,
62
+ left: 0,
63
+ right: 0,
64
+ zIndex: 1000,
65
+ flexDirection: 'row',
66
+ justifyContent: 'space-between',
67
+ paddingHorizontal: theme.spacing.md,
68
+ // Add some top padding to avoid status bar overlap if not handled by safe area
69
+ // but typically the parent container handles this. We'll use safe area value in component.
70
+ };
71
+ const navButton = {
72
+ width: 40,
73
+ height: 40,
74
+ alignItems: 'center',
75
+ justifyContent: 'center',
76
+ borderRadius: 20,
77
+ backgroundColor: theme.colors.surface,
78
+ ...theme.shadows.small,
79
+ };
80
+ const navButtonText = {
81
+ fontSize: 24,
82
+ color: theme.colors.text,
83
+ lineHeight: 28,
84
+ fontWeight: 'bold',
85
+ };
86
+ return {
87
+ containerStyle,
88
+ headerStyle,
89
+ headerTextStyle,
90
+ messageListStyle,
91
+ emptyStateStyle,
92
+ emptyStateTextStyle,
93
+ errorStyle,
94
+ errorTextStyle,
95
+ typingIndicatorContainer,
96
+ navigationContainer,
97
+ navButton,
98
+ navButtonText,
99
+ };
100
+ };
101
+ exports.getWidgetStyles = getWidgetStyles;