@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,461 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSDK = exports.QafkaSDK = void 0;
4
+ const storage_1 = require("./services/storage");
5
+ const BackendService_1 = require("./services/BackendService");
6
+ const AttestationManager_1 = require("./services/AttestationManager");
7
+ const ConversationManager_1 = require("./services/ConversationManager");
8
+ const NavigationHandler_1 = require("./services/NavigationHandler");
9
+ /**
10
+ * QafkaSDK - Main SDK class
11
+ * Provides AI-powered conversational interface for React Native apps
12
+ */
13
+ class QafkaSDK {
14
+ static instance = null;
15
+ config = null;
16
+ status = 'uninitialized';
17
+ subProjectId = null;
18
+ backendService = null;
19
+ attestationManager = null;
20
+ conversationManager = null;
21
+ navigationHandler = null;
22
+ // Theme fetch is fired during initialize() in parallel with attestation so
23
+ // ChatPage can render the branded greeting without waiting for SDK ready.
24
+ // See `useProjectTheme` which awaits this promise.
25
+ themePrefetchPromise = null;
26
+ constructor() { }
27
+ /**
28
+ * Get singleton instance
29
+ */
30
+ static getInstance() {
31
+ if (!QafkaSDK.instance) {
32
+ QafkaSDK.instance = new QafkaSDK();
33
+ }
34
+ return QafkaSDK.instance;
35
+ }
36
+ /**
37
+ * Initialize SDK
38
+ */
39
+ async initialize(config) {
40
+ if (this.status === 'ready') {
41
+ const currentApiKey = this.config?.apiKey ?? null;
42
+ const currentSubProjectId = this.subProjectId;
43
+ const newApiKey = config.apiKey ?? null;
44
+ const newSubProjectId = config.subProjectId ?? null;
45
+ if (currentApiKey === newApiKey && currentSubProjectId === newSubProjectId) {
46
+ return;
47
+ }
48
+ await this.destroy();
49
+ }
50
+ this.status = 'initializing';
51
+ this.config = config;
52
+ this.subProjectId = config.subProjectId ?? null;
53
+ try {
54
+ const apiKey = this.resolveApiKey(config);
55
+ // Initialize services synchronously BEFORE any `await` so that the
56
+ // theme-prefetch promise is observable on the next microtask. If this
57
+ // moves after an await, useProjectTheme (in the same render tick) sees
58
+ // `getThemePrefetch() === null` and falls back to the slow path.
59
+ this.backendService = new BackendService_1.BackendService(apiKey, config.apiUrl, this.subProjectId ?? undefined);
60
+ if (config.locale) {
61
+ this.backendService.setLocale(config.locale);
62
+ }
63
+ // Theme prefetch can only run synchronously here when there is an apiKey
64
+ // to authenticate the request. Without one, the credentials the request
65
+ // needs are not ready yet — firing the prefetch now would fail, blank out
66
+ // the greeting, and need a retry. Deferred to after init below.
67
+ if (apiKey) {
68
+ this.themePrefetchPromise = this.prefetchAndCacheTheme().catch(() => null);
69
+ }
70
+ // Save config to storage
71
+ await storage_1.storage.setItem('@qafka/config', JSON.stringify(config));
72
+ this.conversationManager = new ConversationManager_1.ConversationManager();
73
+ await this.conversationManager.initialize();
74
+ this.navigationHandler = new NavigationHandler_1.NavigationHandler(config.navigationRef);
75
+ // Initialize device attestation
76
+ const rawUrl = (config.apiUrl || 'https://api.qafka.com/api/v1').replace(/\/$/, '');
77
+ const attestApiUrl = rawUrl.includes('/api/v1') ? rawUrl : `${rawUrl}/api/v1`;
78
+ this.attestationManager = new AttestationManager_1.AttestationManager({
79
+ apiUrl: attestApiUrl,
80
+ apiKey: apiKey ?? null,
81
+ debug: config.debug,
82
+ });
83
+ try {
84
+ await this.attestationManager.initialize();
85
+ this.backendService.setAttestationManager(this.attestationManager);
86
+ this.backendService.setSessionTokenGetter(() => this.attestationManager.getSessionToken());
87
+ // Deferred theme prefetch — see the apiKey-gated branch above.
88
+ // Now that the session token getter is wired, the request carries the
89
+ // credentials it needs and the prefetch will succeed on first try,
90
+ // hydrating useProjectTheme before the chat surface paints.
91
+ if (!apiKey && !this.themePrefetchPromise) {
92
+ this.themePrefetchPromise = this.prefetchAndCacheTheme().catch(() => null);
93
+ }
94
+ }
95
+ catch (error) {
96
+ // The unsupported-device fallback only applies on simulators/emulators.
97
+ // On real devices, attestation errors must propagate — even in __DEV__ —
98
+ // so that initialization cannot silently continue unverified.
99
+ if (this.attestationManager.isDevBypass()) {
100
+ if (__DEV__) {
101
+ console.warn('[Qafka] Attestation not supported on this device, continuing without it.');
102
+ }
103
+ }
104
+ else {
105
+ throw error;
106
+ }
107
+ }
108
+ this.status = 'ready';
109
+ if (config.onStatusChange) {
110
+ config.onStatusChange('ready');
111
+ }
112
+ }
113
+ catch (error) {
114
+ this.status = 'error';
115
+ if (config.onStatusChange) {
116
+ config.onStatusChange('error');
117
+ }
118
+ // In dev/debug mode, surface the full error for diagnostic value. In
119
+ // production builds, log only a generic marker so internal error
120
+ // messages do not leak. Integrators receive the categorised status
121
+ // via `onStatusChange('error')` regardless.
122
+ if (__DEV__ || config.debug) {
123
+ console.warn('[Qafka] Initialization failed:', error);
124
+ }
125
+ else {
126
+ console.warn('[Qafka] Initialization failed');
127
+ }
128
+ const detail = error instanceof Error ? error.message : String(error);
129
+ throw new Error(`Failed to initialize QafkaSDK: ${detail}`);
130
+ }
131
+ }
132
+ /**
133
+ * developer-driven locale (BCP 47 — e.g. "tr", "en", "tr-TR").
134
+ * Forwarded to BackendService so it lands in `sdkContext.locale` on every
135
+ * outgoing chat request, which the server then echoes into the user-message
136
+ * context prefix. Same value should drive UI strings on the SDK side.
137
+ *
138
+ * Pass `null` to clear (e.g. when switching back to instructions-default).
139
+ * Calling before initialize() is a no-op; pass `locale` via SDKConfig instead.
140
+ */
141
+ setLocale(locale) {
142
+ this.backendService?.setLocale(locale);
143
+ }
144
+ /**
145
+ * Tool Data Channel: register the partner-provided resolver
146
+ * that supplies opaque PII bags for `{{tooldata.X}}` substitution on
147
+ * the backend. Called from `Qafka.tsx` whenever the `onToolDataRequested`
148
+ * prop changes. Pass `null` to clear.
149
+ */
150
+ setOnToolDataRequested(resolver) {
151
+ this.backendService?.setOnToolDataRequested(resolver);
152
+ }
153
+ /**
154
+ * Returns the current device-attestation session token (or `null` when
155
+ * attestation is unavailable). Voice mode passes this in the auth frame so
156
+ * the realtime WS opens against an attested session.
157
+ */
158
+ async getSessionToken() {
159
+ if (!this.attestationManager)
160
+ return null;
161
+ return this.attestationManager.getSessionToken();
162
+ }
163
+ /**
164
+ * Propagate the validated end-user identity from the React component
165
+ * into the transport layer. Called by `<Qafka />` in an effect whenever
166
+ * the `endUserId` / `endUserData` props change. Validation happens at
167
+ * the component boundary (`validate-end-user.ts`).
168
+ */
169
+ setEndUser(endUserId, endUserData) {
170
+ this.backendService?.setEndUser(endUserId, endUserData);
171
+ }
172
+ /**
173
+ * Send a chat message
174
+ */
175
+ async sendMessage(message, context, contextDescription, isInitialMessage) {
176
+ if (this.status !== 'ready') {
177
+ throw new Error('SDK not initialized. Call initialize() first.');
178
+ }
179
+ if (!message.trim()) {
180
+ throw new Error('Message cannot be empty');
181
+ }
182
+ try {
183
+ // Skip conversation manager for initial messages (no DB save, no local history)
184
+ if (!isInitialMessage) {
185
+ const userMessage = {
186
+ id: this.generateId(),
187
+ role: 'user',
188
+ text: message,
189
+ content: message,
190
+ timestamp: new Date(),
191
+ };
192
+ this.conversationManager?.addMessage(userMessage);
193
+ }
194
+ // Send to backend
195
+ const sessionId = (await this.conversationManager?.getSessionId()) || this.generateId();
196
+ const response = await this.backendService.sendMessage(message, sessionId, context, contextDescription, isInitialMessage);
197
+ // Add to conversation (skip for initial messages)
198
+ if (!isInitialMessage) {
199
+ this.conversationManager?.addMessage({
200
+ id: response.id,
201
+ role: 'assistant',
202
+ text: response.text || '',
203
+ content: response.text || '',
204
+ timestamp: response.timestamp,
205
+ metadata: {},
206
+ });
207
+ }
208
+ // Handle navigation if suggested
209
+ if (response.navigationSuggestion && this.config?.onNavigationSuggest) {
210
+ this.config.onNavigationSuggest(response.navigationSuggestion);
211
+ }
212
+ return response;
213
+ }
214
+ catch (error) {
215
+ throw error;
216
+ }
217
+ }
218
+ /**
219
+ * Send message with streaming response
220
+ */
221
+ async sendMessageStream(message, onChunk, onComplete, onError, context, contextDescription, onToolSuggestions, // Tool suggestions callback
222
+ isInitialMessage, onActionResult, // Action result callback
223
+ onStepCompleted, // Step completed callback
224
+ onFileUploadRequest, // File upload request callback
225
+ onExtractionResult, // Extraction result callback
226
+ // three new stream events
227
+ onToolStatus, onToolResultPayload, onFinalChunk) {
228
+ if (this.status !== 'ready') {
229
+ throw new Error('SDK not initialized. Call initialize() first.');
230
+ }
231
+ if (!message.trim()) {
232
+ throw new Error('Message cannot be empty');
233
+ }
234
+ try {
235
+ // Skip conversation manager for initial messages
236
+ if (!isInitialMessage) {
237
+ const userMessage = {
238
+ id: this.generateId(),
239
+ role: 'user',
240
+ text: message,
241
+ content: message,
242
+ timestamp: new Date(),
243
+ };
244
+ this.conversationManager?.addMessage(userMessage);
245
+ }
246
+ // Skip local AI for streaming, always use backend
247
+ const sessionId = (await this.conversationManager?.getSessionId()) || this.generateId();
248
+ await this.backendService.sendMessageStream(message, sessionId, onChunk, (response) => {
249
+ // Add to conversation (skip for initial messages)
250
+ if (!isInitialMessage) {
251
+ this.conversationManager?.addMessage({
252
+ id: response.id,
253
+ role: 'assistant',
254
+ text: response.text || '',
255
+ content: response.text || '',
256
+ timestamp: response.timestamp,
257
+ metadata: {},
258
+ });
259
+ }
260
+ // Handle navigation if suggested
261
+ if (response.navigationSuggestion &&
262
+ this.config?.onNavigationSuggest) {
263
+ this.config.onNavigationSuggest(response.navigationSuggestion);
264
+ }
265
+ onComplete?.(response);
266
+ }, (_error) => {
267
+ onError?.(_error);
268
+ }, context, contextDescription, onToolSuggestions, // Pass tool suggestions callback
269
+ isInitialMessage, onActionResult, // Pass action result callback
270
+ onStepCompleted, // Pass step completed callback
271
+ onFileUploadRequest, // Pass file upload request callback
272
+ onExtractionResult, // Pass extraction result callback
273
+ onToolStatus, //
274
+ onToolResultPayload, //
275
+ onFinalChunk);
276
+ }
277
+ catch (error) {
278
+ if (onError) {
279
+ onError(error instanceof Error ? error : new Error('Unknown error'));
280
+ }
281
+ else {
282
+ throw error;
283
+ }
284
+ }
285
+ }
286
+ /**
287
+ * Get conversation history
288
+ */
289
+ async getConversationHistory() {
290
+ if (this.status !== 'ready') {
291
+ throw new Error('SDK not initialized. Call initialize() first.');
292
+ }
293
+ if (!this.conversationManager) {
294
+ throw new Error('Conversation manager not initialized');
295
+ }
296
+ return this.conversationManager.getHistory();
297
+ }
298
+ /**
299
+ * Clear conversation history
300
+ */
301
+ async clearConversation() {
302
+ if (this.status !== 'ready') {
303
+ throw new Error('SDK not initialized. Call initialize() first.');
304
+ }
305
+ if (!this.conversationManager) {
306
+ throw new Error('Conversation manager not initialized');
307
+ }
308
+ await this.conversationManager.clear();
309
+ }
310
+ /**
311
+ * Start a new conversation
312
+ * Clears history and returns the new session ID
313
+ */
314
+ async startNewConversation() {
315
+ if (this.status !== 'ready') {
316
+ throw new Error('SDK not initialized. Call initialize() first.');
317
+ }
318
+ if (!this.conversationManager) {
319
+ throw new Error('Conversation manager not initialized');
320
+ }
321
+ await this.conversationManager.clear();
322
+ return this.conversationManager.getSessionId();
323
+ }
324
+ /**
325
+ * Add tool response to conversation
326
+ * Use this after executing a tool to display the results
327
+ */
328
+ addToolResponse(toolKey, data, tool) {
329
+ if (this.status !== 'ready') {
330
+ throw new Error('SDK not initialized. Call initialize() first.');
331
+ }
332
+ if (!this.conversationManager) {
333
+ throw new Error('Conversation manager not initialized');
334
+ }
335
+ const message = {
336
+ id: this.generateId(),
337
+ role: 'assistant',
338
+ text: '', // Will be rendered by component
339
+ content: '',
340
+ timestamp: new Date(),
341
+ toolResponse: {
342
+ toolKey,
343
+ data,
344
+ tool,
345
+ },
346
+ };
347
+ this.conversationManager.addMessage(message);
348
+ return message;
349
+ }
350
+ /**
351
+ * Post a tool result back to the backend so the AI can continue its turn.
352
+ */
353
+ async postToolResult(payload, onFinalChunk, onDone, onError, onCard) {
354
+ if (this.status !== 'ready') {
355
+ throw new Error('SDK not initialized. Call initialize() first.');
356
+ }
357
+ if (!this.backendService) {
358
+ throw new Error('Backend service not initialized');
359
+ }
360
+ return this.backendService.postToolResult(payload, onFinalChunk, onDone, onError, onCard);
361
+ }
362
+ /**
363
+ * Upload a file attached to a tool flow.
364
+ */
365
+ async uploadFile(toolId, file, conversationId) {
366
+ if (this.status !== 'ready') {
367
+ throw new Error('SDK not initialized. Call initialize() first.');
368
+ }
369
+ return this.backendService.uploadFile(toolId, file, conversationId);
370
+ }
371
+ /**
372
+ * Get project theme configuration
373
+ */
374
+ async getProjectTheme() {
375
+ if (this.status !== 'ready') {
376
+ throw new Error('SDK not initialized. Call initialize() first.');
377
+ }
378
+ if (!this.backendService) {
379
+ throw new Error('Backend service not initialized');
380
+ }
381
+ return this.backendService.getTheme();
382
+ }
383
+ /**
384
+ * Returns the in-flight theme prefetch (kicked off in initialize() parallel
385
+ * with attestation). Used by `useProjectTheme` to paint the branded greeting
386
+ * without waiting for `status === 'ready'`. Returns `null` if initialize()
387
+ * hasn't been called yet or if prefetch already settled.
388
+ */
389
+ getThemePrefetch() {
390
+ return this.themePrefetchPromise;
391
+ }
392
+ /**
393
+ * Fetches the project theme via BackendService and writes the result to
394
+ * persistent cache so subsequent chat opens paint instantly.
395
+ */
396
+ async prefetchAndCacheTheme() {
397
+ if (!this.backendService)
398
+ return null;
399
+ const themeData = await this.backendService.getTheme();
400
+ if (themeData) {
401
+ try {
402
+ const cacheKey = `@qafka/theme:${this.subProjectId ?? 'main'}`;
403
+ await storage_1.storage.setItem(cacheKey, JSON.stringify(themeData));
404
+ }
405
+ catch {
406
+ // Cache write failures are non-fatal — live theme is still in memory.
407
+ }
408
+ }
409
+ return themeData;
410
+ }
411
+ /**
412
+ * Get SDK status
413
+ */
414
+ getStatus() {
415
+ return this.status;
416
+ }
417
+ /**
418
+ * Get SDK configuration
419
+ */
420
+ getConfig() {
421
+ return this.config;
422
+ }
423
+ /**
424
+ * Navigate to screen
425
+ */
426
+ navigate(screenName, params) {
427
+ if (!this.navigationHandler) {
428
+ throw new Error('Navigation handler not initialized');
429
+ }
430
+ this.navigationHandler.navigate(screenName, params);
431
+ }
432
+ /**
433
+ * Generate unique ID
434
+ */
435
+ generateId() {
436
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
437
+ }
438
+ /**
439
+ * Resolve API key from config
440
+ */
441
+ resolveApiKey(config) {
442
+ return config.apiKey ?? null;
443
+ }
444
+ /**
445
+ * Destroy SDK instance
446
+ */
447
+ async destroy() {
448
+ this.backendService = null;
449
+ this.conversationManager = null;
450
+ this.navigationHandler = null;
451
+ this.subProjectId = null;
452
+ this.config = null;
453
+ this.themePrefetchPromise = null;
454
+ this.status = 'uninitialized';
455
+ QafkaSDK.instance = null;
456
+ }
457
+ }
458
+ exports.QafkaSDK = QafkaSDK;
459
+ // Export singleton instance getter
460
+ const getSDK = () => QafkaSDK.getInstance();
461
+ exports.getSDK = getSDK;
@@ -0,0 +1,25 @@
1
+ import type { FieldName } from "../types";
2
+ /**
3
+ * Resolves a fieldName string against a data object.
4
+ *
5
+ * Grammar:
6
+ * - Plain dot notation: `user.profile.name`
7
+ * - Leading dot ".": current iteration item (used inside QList itemTemplate later)
8
+ * - Returns `undefined` for missing paths; renderer skips-on-undefined for optional bindings.
9
+ */
10
+ export declare function resolveFieldName(fieldName: FieldName | undefined, data: unknown, iterationItem?: unknown): unknown;
11
+ /**
12
+ * Resolves `{{fieldName}}` placeholders inside a string template against data.
13
+ * Used for CTA params/labels where binding is embedded in a value, not a separate prop.
14
+ */
15
+ export declare function resolveTemplateString(template: string, data: unknown, iterationItem?: unknown): string;
16
+ /**
17
+ * Walks a value (string / object / array) and resolves any embedded `{{...}}` templates.
18
+ * Used for CTA action configs where params object may contain templates at any depth.
19
+ */
20
+ export declare function resolveDeep<T>(value: T, data: unknown, iterationItem?: unknown): T;
21
+ /**
22
+ * Truthiness check for `showIf` bindings — string "false"/"0"/"" are treated as false,
23
+ * matching common JSON serialization edge cases.
24
+ */
25
+ export declare function evaluateShowIf(showIf: FieldName | undefined, data: unknown, iterationItem?: unknown): boolean;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveFieldName = resolveFieldName;
4
+ exports.resolveTemplateString = resolveTemplateString;
5
+ exports.resolveDeep = resolveDeep;
6
+ exports.evaluateShowIf = evaluateShowIf;
7
+ /**
8
+ * Resolves a fieldName string against a data object.
9
+ *
10
+ * Grammar:
11
+ * - Plain dot notation: `user.profile.name`
12
+ * - Leading dot ".": current iteration item (used inside QList itemTemplate later)
13
+ * - Returns `undefined` for missing paths; renderer skips-on-undefined for optional bindings.
14
+ */
15
+ function resolveFieldName(fieldName, data, iterationItem) {
16
+ if (!fieldName)
17
+ return undefined;
18
+ const usesIteration = fieldName.startsWith(".");
19
+ const target = usesIteration ? iterationItem : data;
20
+ const path = usesIteration ? fieldName.slice(1) : fieldName;
21
+ if (target == null)
22
+ return undefined;
23
+ if (path === "")
24
+ return target;
25
+ return path.split(".").reduce((acc, segment) => {
26
+ if (acc == null || typeof acc !== "object")
27
+ return undefined;
28
+ return acc[segment];
29
+ }, target);
30
+ }
31
+ /**
32
+ * Resolves `{{fieldName}}` placeholders inside a string template against data.
33
+ * Used for CTA params/labels where binding is embedded in a value, not a separate prop.
34
+ */
35
+ function resolveTemplateString(template, data, iterationItem) {
36
+ return template.replace(/\{\{([^}]+)\}\}/g, (_, expr) => {
37
+ const trimmed = expr.trim();
38
+ const value = resolveFieldName(trimmed, data, iterationItem);
39
+ return value == null ? "" : String(value);
40
+ });
41
+ }
42
+ /**
43
+ * Walks a value (string / object / array) and resolves any embedded `{{...}}` templates.
44
+ * Used for CTA action configs where params object may contain templates at any depth.
45
+ */
46
+ function resolveDeep(value, data, iterationItem) {
47
+ if (typeof value === "string") {
48
+ return resolveTemplateString(value, data, iterationItem);
49
+ }
50
+ if (Array.isArray(value)) {
51
+ return value.map((v) => resolveDeep(v, data, iterationItem));
52
+ }
53
+ if (value != null && typeof value === "object") {
54
+ const out = {};
55
+ for (const [k, v] of Object.entries(value)) {
56
+ out[k] = resolveDeep(v, data, iterationItem);
57
+ }
58
+ return out;
59
+ }
60
+ return value;
61
+ }
62
+ /**
63
+ * Truthiness check for `showIf` bindings — string "false"/"0"/"" are treated as false,
64
+ * matching common JSON serialization edge cases.
65
+ */
66
+ function evaluateShowIf(showIf, data, iterationItem) {
67
+ if (showIf === undefined)
68
+ return true;
69
+ const value = resolveFieldName(showIf, data, iterationItem);
70
+ if (value === undefined || value === null)
71
+ return false;
72
+ if (typeof value === "string") {
73
+ const normalized = value.toLowerCase().trim();
74
+ if (normalized === "" || normalized === "false" || normalized === "0")
75
+ return false;
76
+ }
77
+ if (typeof value === "number")
78
+ return value !== 0;
79
+ if (Array.isArray(value))
80
+ return value.length > 0;
81
+ return Boolean(value);
82
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import type { CardCTAHostCallbacks, CardCTALifecycle } from "./types";
3
+ export interface CardRuntimeContextValue {
4
+ data: unknown;
5
+ cardTemplateId: string;
6
+ cardSlug: string;
7
+ messageId?: string;
8
+ hostCallbacks: CardCTAHostCallbacks;
9
+ lifecycle: CardCTALifecycle;
10
+ dismiss: () => void;
11
+ }
12
+ export declare function CardRuntimeProvider({ value, children, }: {
13
+ value: CardRuntimeContextValue;
14
+ children: React.ReactNode;
15
+ }): React.ReactElement;
16
+ export declare function useCardRuntime(): CardRuntimeContextValue;
@@ -0,0 +1,58 @@
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.CardRuntimeProvider = CardRuntimeProvider;
37
+ exports.useCardRuntime = useCardRuntime;
38
+ const react_1 = __importStar(require("react"));
39
+ const CardRuntimeContext = (0, react_1.createContext)(null);
40
+ function CardRuntimeProvider({ value, children, }) {
41
+ const memo = (0, react_1.useMemo)(() => value, [
42
+ value.data,
43
+ value.cardTemplateId,
44
+ value.cardSlug,
45
+ value.messageId,
46
+ value.hostCallbacks,
47
+ value.lifecycle,
48
+ value.dismiss,
49
+ ]);
50
+ return (<CardRuntimeContext.Provider value={memo}>{children}</CardRuntimeContext.Provider>);
51
+ }
52
+ function useCardRuntime() {
53
+ const ctx = (0, react_1.useContext)(CardRuntimeContext);
54
+ if (!ctx) {
55
+ throw new Error("useCardRuntime must be used inside CardRuntimeProvider");
56
+ }
57
+ return ctx;
58
+ }
@@ -0,0 +1,7 @@
1
+ import type { CTADispatcher } from "./types";
2
+ /**
3
+ * SDK-internal CTA dispatcher.
4
+ * Resolves embedded `{{fieldName}}` templates against the card's data context at click time.
5
+ * Falls back to host callbacks for actions that cross the SDK boundary (deep_link, tool_trigger).
6
+ */
7
+ export declare const dispatchCTA: CTADispatcher;