@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,744 @@
1
+ import { ViewStyle } from 'react-native';
2
+ import { Theme, ThemeOverride } from '../themes';
3
+ import { ComponentRegistry } from '../types/components';
4
+ import { ExternalSuggestion } from '../types/external-navigation';
5
+ import { NavigationSuggestion } from '../types/navigation';
6
+ import { VoiceChatState } from './VoicePage';
7
+ /**
8
+ * Augmentation hook for consumer-side projectId literal types.
9
+ *
10
+ * The Qafka CLI generates a `qafka.config.d.ts` file alongside
11
+ * `qafka.config.js` whenever it writes the runtime config. That `.d.ts`
12
+ * augments this interface with the consumer's project ids as keys —
13
+ * giving `<Qafka projectId="..." />` autocomplete + typo validation.
14
+ *
15
+ * When the augmentation is absent (fresh project, `qafka init` not yet
16
+ * run), `keyof QafkaProjectIds` is `never` and `ProjectIdOf` falls back
17
+ * to plain `string` for backwards compatibility.
18
+ *
19
+ * Consumers don't interact with this interface directly — the CLI manages it.
20
+ */
21
+ export interface QafkaProjectIds {
22
+ }
23
+ /**
24
+ * Resolves to the consumer's registered project ids (literal union) when
25
+ * the CLI-generated `qafka.config.d.ts` has populated `QafkaProjectIds`,
26
+ * or to `string` otherwise.
27
+ */
28
+ export type ProjectIdOf = [keyof QafkaProjectIds] extends [never] ? string : keyof QafkaProjectIds;
29
+ /**
30
+ * Controls what `addResponse` does inside an `onToolSuggested` handler.
31
+ * Same semantics in both voice and text chat.
32
+ *
33
+ * - `"ui"`: Only update the rendered tool UI / chat card. Does NOT close the
34
+ * tool flow. Use for progress updates, optimistic state, or partial render
35
+ * before the real result arrives. You MUST eventually call `addResponse`
36
+ * again with `"both"` or `"data"` to close the tool out.
37
+ * - `"data"`: Commit the tool flow without touching the UI.
38
+ * - `"both"` (default): UI render + commit.
39
+ */
40
+ export type ToolResponseMode = 'ui' | 'data' | 'both';
41
+ /**
42
+ * Props passed to custom voice indicator component.
43
+ *
44
+ * `isMuted` reflects the user's manual mute toggle, not the internal
45
+ * tool-flow mute. Use it to dim or otherwise mark the indicator while the
46
+ * user has the mic off.
47
+ */
48
+ export interface VoiceIndicatorProps {
49
+ state: VoiceChatState;
50
+ amplitude: number;
51
+ theme: Theme;
52
+ isMuted?: boolean;
53
+ }
54
+ /**
55
+ * Props passed to a custom voice mute button. Same shape across all three
56
+ * VoicePage layouts (centered orb, chat layout, compact tool bar); the
57
+ * layout decides where to place it, the component only renders the control.
58
+ */
59
+ export interface VoiceMuteButtonProps {
60
+ isMuted: boolean;
61
+ onToggle: () => void;
62
+ theme: Theme;
63
+ state: VoiceChatState;
64
+ }
65
+ /**
66
+ * Props passed to custom voice background component
67
+ */
68
+ export interface VoiceBackgroundProps {
69
+ state: VoiceChatState;
70
+ amplitude: number;
71
+ theme: Theme;
72
+ children: React.ReactNode;
73
+ }
74
+ /**
75
+ * One completed conversational turn captured during a voice session. Live
76
+ * (still-streaming) AI text and just-finalized-but-not-yet-pushed user text
77
+ * are NOT in here — those are still on `transcript` / `userTranscript`.
78
+ */
79
+ export interface VoiceTranscriptTurn {
80
+ id: string;
81
+ role: 'user' | 'assistant';
82
+ text: string;
83
+ }
84
+ /**
85
+ * Render mode for the voice transcript area.
86
+ *
87
+ * - `"centered"`: a single line of the current AI transcript centered above
88
+ * the state label. Minimal vertical footprint. No history.
89
+ * - `"chat"`: scrolling chat history of both user and AI turns, lightweight
90
+ * (no bubble backgrounds, role-aligned, older turns fade) so it stays
91
+ * visually distinct from text mode. While a tool is rendered on screen,
92
+ * the chat collapses automatically — the compact status bar takes over,
93
+ * and the chat reappears when the tool UI is dismissed.
94
+ * - `"off"`: transcripts and state label hidden. Only the orb is visible.
95
+ */
96
+ export type VoiceTranscriptMode = 'off' | 'centered' | 'chat';
97
+ /**
98
+ * Props passed to custom voice transcript component.
99
+ *
100
+ * `history` and `mode` are only populated when the consumer opts into
101
+ * `voiceTranscript="chat"`. For the default `"centered"` mode, the custom
102
+ * component should rely on the live `transcript` string only.
103
+ */
104
+ export interface VoiceTranscriptProps {
105
+ transcript: string;
106
+ userTranscript: string;
107
+ state: VoiceChatState;
108
+ theme: Theme;
109
+ mode?: VoiceTranscriptMode;
110
+ history?: VoiceTranscriptTurn[];
111
+ }
112
+ /**
113
+ * Custom voice components registry
114
+ * Allows customers to provide their own Lottie, GIF, Image, or any React component
115
+ * for the voice chat UI. If not provided, defaults are used.
116
+ */
117
+ export interface VoiceComponents {
118
+ VoiceIndicator?: React.ComponentType<VoiceIndicatorProps>;
119
+ VoiceBackground?: React.ComponentType<VoiceBackgroundProps>;
120
+ VoiceTranscript?: React.ComponentType<VoiceTranscriptProps>;
121
+ /**
122
+ * Slot for the manual mute toggle rendered on the right side of the orb
123
+ * row in every voice layout. Defaults to a circular icon button.
124
+ */
125
+ VoiceMuteButton?: React.ComponentType<VoiceMuteButtonProps>;
126
+ /**
127
+ * Slot for the multi-chip list rendered when the AI calls the built-in
128
+ * `qafka_display` tool with mode="chip".
129
+ */
130
+ dataChipList?: React.ComponentType<{
131
+ items: Array<{
132
+ label: string;
133
+ value: string;
134
+ copyable?: boolean;
135
+ }>;
136
+ theme?: any;
137
+ }>;
138
+ /**
139
+ * Slot for an individual chip used inside the default DataChipList.
140
+ */
141
+ dataChip?: React.ComponentType<{
142
+ label: string;
143
+ value: string;
144
+ copyable?: boolean;
145
+ theme?: any;
146
+ }>;
147
+ }
148
+ /**
149
+ * Imperative handle returned by Qafka via `ref`.
150
+ *
151
+ * Use when your custom logic has async work outside of `onToolSuggested`
152
+ * (e.g. the developer is driving a multi-step flow manually).
153
+ *
154
+ * @example
155
+ * const qafkaRef = useRef<QafkaHandle>(null);
156
+ * qafkaRef.current?.setLoading(true, 'Fetching data...');
157
+ * // ...do work...
158
+ * qafkaRef.current?.setLoading(false);
159
+ *
160
+ * <Qafka ref={qafkaRef} />
161
+ */
162
+ export interface QafkaHandle {
163
+ /**
164
+ * Manually control the voice-mode loading pill + mic pause state.
165
+ * Use when your custom logic has async work outside of onToolSuggested.
166
+ *
167
+ * @param visible - true to show pill + pause mic, false to hide + resume
168
+ * @param message - optional pill text. Defaults to "Processing…" when omitted.
169
+ */
170
+ setLoading(visible: boolean, message?: string): void;
171
+ /**
172
+ * Clear all currently rendered voice tool UI cards. Useful when starting
173
+ * a fresh interaction or when the consumer wants to reset the view
174
+ * outside of the tool-result flow.
175
+ */
176
+ clearRenderedTools(): void;
177
+ /**
178
+ * Send a text message to the AI as if the user typed it. Triggers the
179
+ * normal message-send pipeline — streaming reply, tool suggestions, etc.
180
+ * Useful for quick-reply buttons, deep links, or programmatic prompts.
181
+ */
182
+ sendMessage(text: string): void;
183
+ /**
184
+ * Open the realtime voice session programmatically. Same as the user
185
+ * tapping the mic button.
186
+ */
187
+ connectVoice(): Promise<void>;
188
+ /**
189
+ * Close the realtime voice session programmatically. Safe to call when
190
+ * already disconnected (no-op).
191
+ */
192
+ disconnectVoice(): Promise<void>;
193
+ /**
194
+ * Pause microphone capture without ending the voice session. The AI keeps
195
+ * speaking, the user just cannot be heard until {@link resumeMic} is
196
+ * called. Useful for push-to-talk patterns or letting the user mute
197
+ * temporarily.
198
+ */
199
+ pauseMic(): Promise<void>;
200
+ /**
201
+ * Resume microphone capture after a {@link pauseMic} call. Idempotent —
202
+ * safe to call when the mic is already active.
203
+ */
204
+ resumeMic(): Promise<void>;
205
+ /**
206
+ * Manually toggle the user-controlled mute. Independent from the internal
207
+ * tool-flow mute the SDK applies during AI / tool transitions — user mute
208
+ * persists across those, matching Teams/Zoom semantics.
209
+ */
210
+ toggleMute(): void;
211
+ /**
212
+ * Engage the user mute. Idempotent.
213
+ */
214
+ mute(): void;
215
+ /**
216
+ * Lift the user mute. Idempotent. If a tool flow is currently muting the
217
+ * mic internally, the mic stays off until that flow completes.
218
+ */
219
+ unmute(): void;
220
+ /**
221
+ * Whether the user has the mic muted right now. Reflects the user toggle
222
+ * only, not the internal tool-flow mute.
223
+ */
224
+ readonly isMuted: boolean;
225
+ }
226
+ /**
227
+ * Props for Qafka Component
228
+ *
229
+ * @example
230
+ * // Basic usage
231
+ * <Qafka projectId="proj_abc123" />
232
+ */
233
+ export interface QafkaProps {
234
+ style?: ViewStyle;
235
+ /**
236
+ * Project identifier when the runtime config registers more than one
237
+ * project. Selects which project's development key the SDK uses in
238
+ * development builds (loaded from `qafka.config.js`).
239
+ *
240
+ * Leave undefined to use the runtime config's default project. Pass a
241
+ * project id that is unknown to the runtime config and the SDK throws on
242
+ * mount — silent fallback would mask a typo.
243
+ *
244
+ * In production builds this prop is ignored by the key-resolution path,
245
+ * which does not rely on a bundled key.
246
+ *
247
+ * When the Qafka CLI has been run (`qafka init` / `qafka project` /
248
+ * `qafka sync`), this prop is narrowed to the registered project ids —
249
+ * giving autocomplete and typo validation. Without the CLI augmentation
250
+ * the type falls back to `string` for backwards compatibility.
251
+ *
252
+ * @example "proj_abc123"
253
+ */
254
+ projectId?: ProjectIdOf;
255
+ /**
256
+ * Backend API URL (OPTIONAL — advanced, leave unset for production).
257
+ */
258
+ apiUrl?: string;
259
+ /**
260
+ * Sub-Project Identifier (OPTIONAL)
261
+ *
262
+ * Used for multi-tenant setups where a single project serves multiple
263
+ * sub-projects (e.g., per-region or per-tenant rollouts).
264
+ *
265
+ * When provided, sub-project routing is applied server-side.
266
+ * If omitted, the parent project is used directly (backward compatible).
267
+ *
268
+ * @example "tenant-a"
269
+ */
270
+ subProjectId?: string;
271
+ /**
272
+ * BCP 47 locale (e.g. "tr", "en", "tr-TR").
273
+ *
274
+ * Drives UI strings (placeholder, buttons, dashboard-supplied multi-locale
275
+ * greeting) and is forwarded to the backend as part of `sdkContext.locale`.
276
+ * Pair with the server-side language priority rule: when set, the AI
277
+ * answers in this locale unless project instructions explicitly demand
278
+ * another language.
279
+ *
280
+ * Leave undefined to fall back to whatever language the project
281
+ * instructions are written in.
282
+ *
283
+ * @example "tr"
284
+ * @example "en-US"
285
+ */
286
+ locale?: string;
287
+ /**
288
+ * Widget display mode
289
+ */
290
+ mode?: 'fullscreen' | 'inline' | 'floating';
291
+ /**
292
+ * Theme mode or custom theme object
293
+ */
294
+ theme?: 'light' | 'dark' | Theme;
295
+ /**
296
+ * Theme overrides (partial theme)
297
+ * Allows overriding specific theme properties without replacing the entire theme
298
+ * @example { colors: { background: '#FF0000' } }
299
+ */
300
+ themeOverride?: ThemeOverride;
301
+ /**
302
+ * Full custom theme that replaces the default theme entirely. Pair this with
303
+ * `themeOverride` only when you need to merge partial overrides on top.
304
+ */
305
+ customTheme?: Theme;
306
+ /**
307
+ * Enable streaming responses (real-time typing effect)
308
+ */
309
+ enableStreaming?: boolean;
310
+ /**
311
+ * Whether the current user is authenticated.
312
+ * Controls which screens are suggested in navigation:
313
+ * - true: authenticated screens suggested, unauthenticated screens hidden
314
+ * - false: unauthenticated screens suggested, authenticated screens hidden
315
+ * - undefined: backward compatible behavior (all screens suggested)
316
+ *
317
+ * @example isAuthenticated={!!user}
318
+ */
319
+ isAuthenticated?: boolean;
320
+ /**
321
+ * Stable identifier for the end user using this SDK instance.
322
+ *
323
+ * Required. The backend uses this id to group conversations, action logs,
324
+ * and audience analytics per user. Send a constant placeholder
325
+ * (`"anonymous"`, `"anon-${deviceId}"`, ...) before the user signs in so
326
+ * pre-login activity still has a stable lane.
327
+ *
328
+ * The value is validated on mount and on every chat request:
329
+ * - empty / null / whitespace → runtime Error
330
+ * - longer than 256 characters → runtime Error
331
+ *
332
+ * Numbers are accepted and coerced to string before they leave the SDK.
333
+ *
334
+ * NOT forwarded to the LLM prompt — operator dashboards and tool action
335
+ * templates can reach it via `{{endUser.id}}` without leaking it to the AI.
336
+ *
337
+ * @example endUserId={user?.id ?? `anon-${deviceId}`}
338
+ */
339
+ endUserId: string | number;
340
+ /**
341
+ * Optional structured profile data for operator dashboards and tool
342
+ * action templates (`{{endUser.data.<key>}}`).
343
+ *
344
+ * NEVER forwarded to the LLM prompt. If you want the AI to see something,
345
+ * put it in `context` instead.
346
+ *
347
+ * Limited to 8KB when serialized to JSON; exceeding the limit causes the
348
+ * field to be dropped from the request (with a `console.error`) — chat
349
+ * and tracking continue to work.
350
+ *
351
+ * @example endUserData={{ email: 'user@example.com', plan: 'pro' }}
352
+ */
353
+ endUserData?: Record<string, unknown>;
354
+ /**
355
+ * Context object for Tool Registry.
356
+ * Provides runtime context to match and execute tools.
357
+ * @example { userId: '456', selectedItemId: '123' }
358
+ */
359
+ context?: Record<string, any>;
360
+ /**
361
+ * Context description for the AI.
362
+ * Helps the AI understand what the context keys mean.
363
+ * @example "User is on the settings screen"
364
+ */
365
+ contextDescription?: string;
366
+ /**
367
+ * Custom component registry for Tool Registry responses.
368
+ * Register your own components to render tool responses.
369
+ * @example { ProductCard: MyProductCard, OrderCard: MyOrderCard }
370
+ */
371
+ components?: ComponentRegistry;
372
+ /**
373
+ * Show timestamps on messages
374
+ */
375
+ showTimestamps?: boolean;
376
+ /**
377
+ * Input placeholder text
378
+ */
379
+ placeholder?: string;
380
+ /**
381
+ * Header title
382
+ */
383
+ title?: string;
384
+ /**
385
+ * Show header
386
+ */
387
+ showHeader?: boolean;
388
+ /**
389
+ * Maximum message length
390
+ */
391
+ maxMessageLength?: number;
392
+ /**
393
+ * Custom users greeting message (overrides Project API greeting)
394
+ */
395
+ greetingMessage?: string;
396
+ /**
397
+ * Callback when widget is ready
398
+ */
399
+ onReady?: () => void;
400
+ /**
401
+ * Callback when message is sent
402
+ */
403
+ onMessageSent?: (message: string) => void;
404
+ /**
405
+ * Callback when response is received
406
+ */
407
+ onResponseReceived?: (response: any) => void;
408
+ /**
409
+ * Callback on error
410
+ */
411
+ onError?: (error: Error) => void;
412
+ /**
413
+ * Callback when navigation is suggested (always triggered)
414
+ */
415
+ onNavigationSuggest?: (suggestion: NavigationSuggestion) => void;
416
+ /**
417
+ * Callback when navigation button is pressed by user.
418
+ * If not provided, SDK automatically navigates using `suggestion.route` via Expo Router.
419
+ */
420
+ onNavigationAction?: (suggestion: NavigationSuggestion) => void;
421
+ /**
422
+ * Callback when an external suggestion button is pressed by user
423
+ * (WhatsApp, phone, map, app store, etc.).
424
+ *
425
+ * If not provided, the SDK opens the suggestion's `url` via
426
+ * `Linking.openURL`, falling back to `fallbackUrl` when the primary URL
427
+ * is not handleable on the device (e.g. the target app isn't installed).
428
+ *
429
+ * @example
430
+ * onExternalSuggestion={(s) => {
431
+ * analytics.track('external_nav_press', { type: s.type, id: s.id });
432
+ * Linking.openURL(s.url);
433
+ * }}
434
+ */
435
+ onExternalSuggestion?: (suggestion: ExternalSuggestion) => void;
436
+ /**
437
+ * Card Template CTA host callbacks. Invoked when a button
438
+ * inside a rendered card fires an action that crosses the SDK boundary
439
+ * (deep_link, share, suggest_message, copy, external_navigation, tool_trigger).
440
+ *
441
+ * The most common one is `onCardDeepLink` — partner navigates to an internal
442
+ * route from a card button:
443
+ *
444
+ * @example
445
+ * onCardDeepLink={(path) => router.push(path)}
446
+ *
447
+ * `copy` and `share` have sensible RN defaults (Clipboard / Share.share),
448
+ * so partners only register them when they want custom behavior (e.g.
449
+ * show a toast on copy success).
450
+ */
451
+ onCardDeepLink?: (path: string, action?: any) => void | Promise<void>;
452
+ onCardSuggestMessage?: (text: string) => void | Promise<void>;
453
+ onCardExternalNavigation?: (action: any) => void | Promise<void>;
454
+ onCardShare?: (payload: {
455
+ text?: string;
456
+ url?: string;
457
+ }) => void | Promise<void>;
458
+ onCardCopy?: (value: string) => void | Promise<void>;
459
+ /**
460
+ * Invoked when a card button has type "tool_trigger". The host can
461
+ * forward this to the SDK's tool execution path or surface it as a new
462
+ * chat message. Optional; if unset the action is silently skipped.
463
+ */
464
+ onCardToolTrigger?: (toolName: string, params: Record<string, unknown>, meta: {
465
+ ctaDisplayMode: 'user_message' | 'silent';
466
+ }) => void | Promise<void>;
467
+ /**
468
+ * CTA telemetry hook — called once per click. The event payload mirrors
469
+ * `CardCTATelemetryEvent` from the cards module:
470
+ * - `item` carries the bound record (the iterated item in list mode,
471
+ * full tool result in detail mode), so partners don't have to look
472
+ * it up by index.
473
+ * - `itemIndex` is present only in list mode.
474
+ */
475
+ onCardCTAClick?: (event: {
476
+ event: 'cta_click';
477
+ cardTemplateId: string;
478
+ cardSlug: string;
479
+ actionType: string;
480
+ toolName?: string;
481
+ messageId?: string;
482
+ itemIndex?: number;
483
+ item: unknown;
484
+ confirmed: boolean;
485
+ }) => void;
486
+ /**
487
+ * Callback when Tool Registry suggests tools
488
+ * Called during streaming when backend identifies matching tools
489
+ * @param tools - Array of matched tool definitions with relevance scores and customData
490
+ * @param addResponse - Helper function to add tool response to chat
491
+ * @example
492
+ * onToolSuggested={async (tools, addResponse) => {
493
+ * const tool = tools[0];
494
+ * // tool.customData contains any custom data you attached to this tool
495
+ * const response = await fetch(tool.customData.apiBaseUrl + tool.endpoint);
496
+ * addResponse(tool.key, await response.json(), tool);
497
+ * }}
498
+ */
499
+ onToolSuggested?: (tools: any[], addResponse: (data: any, tool: any, mode?: ToolResponseMode) => void) => void | Promise<void>;
500
+ /**
501
+ * Callback when backend action execution results arrive
502
+ * Called during streaming when the backend sends action_result SSE events
503
+ * @param results - Array of action execution results
504
+ * @example
505
+ * onActionResult={(results) => {
506
+ * results.forEach(r => {
507
+ * if (r.success) Toast.show(r.message);
508
+ * });
509
+ * }}
510
+ */
511
+ onActionResult?: (results: Array<{
512
+ actionType: string;
513
+ success: boolean;
514
+ message: string;
515
+ }>) => void;
516
+ /**
517
+ * Tool Data Channel — partner-provided resolver for transient PII bags.
518
+ *
519
+ * Partner-provided resolver invoked when a suggested tool references
520
+ * `{{tooldata.X}}` tokens in its email/webhook body. Returned values are
521
+ * POSTed to backend's request-scoped cache and substituted into action
522
+ * payloads — **never sent to the LLM, never written to the Qafka DB**.
523
+ *
524
+ * Backend signals the need via `needData: true` on the `tool_suggestions`
525
+ * SSE event. SDK calls this resolver, POSTs the resulting opaque bag to
526
+ * `/chat/tool-data`, backend looks it up by `toolExecutionId` when running
527
+ * the tool's actions (email/webhook).
528
+ *
529
+ * Resolver may be sync or async. Throwing or rejecting is fail-soft —
530
+ * SDK posts an empty bag and `{{tooldata.X}}` tokens resolve to empty
531
+ * strings on the server, allowing the action to still run (just without
532
+ * the substituted PII).
533
+ *
534
+ * @param tool Suggested tool descriptor (key + name).
535
+ * @returns Plain object of key → string/number/boolean values, OR a
536
+ * Promise of the same.
537
+ * @example
538
+ * onToolDataRequested={async ({ key }) => {
539
+ * if (key === 'sendInvoice') {
540
+ * const user = await store.getCurrentUser();
541
+ * return { gsm: user.phone, email: user.email };
542
+ * }
543
+ * return {};
544
+ * }}
545
+ */
546
+ onToolDataRequested?: (tool: {
547
+ key: string;
548
+ name: string;
549
+ }) => Record<string, unknown> | Promise<Record<string, unknown>>;
550
+ /** Called when a step side-effect completes during a multi-step tool flow */
551
+ onStepCompleted?: (result: {
552
+ tool: string;
553
+ step: string;
554
+ data: Record<string, any>;
555
+ actionResults?: Array<{
556
+ actionType: string;
557
+ success: boolean;
558
+ message: string;
559
+ }>;
560
+ }) => void;
561
+ /**
562
+ * Called when a tool requires file upload from the user.
563
+ * The app should open its own file picker and call submit() with the selected file.
564
+ *
565
+ * @example
566
+ * onFileUploadRequest={({ toolId, fileInput, submit, cancel }) => {
567
+ * ImagePicker.launchImageLibrary({ mediaType: 'photo' }, (response) => {
568
+ * if (response.assets?.[0]) {
569
+ * submit({
570
+ * uri: response.assets[0].uri!,
571
+ * name: response.assets[0].fileName!,
572
+ * type: response.assets[0].type!,
573
+ * });
574
+ * } else {
575
+ * cancel();
576
+ * }
577
+ * });
578
+ * }}
579
+ */
580
+ onFileUploadRequest?: (request: {
581
+ toolId: string;
582
+ fileInput: {
583
+ accept: string[];
584
+ maxSize: number;
585
+ sources: string[];
586
+ };
587
+ submit: (file: {
588
+ uri: string;
589
+ name: string;
590
+ type: string;
591
+ }) => void;
592
+ cancel: () => void;
593
+ }) => void;
594
+ /**
595
+ * Called when document extraction completes after file upload.
596
+ * Contains the structured data extracted by AI from the uploaded document.
597
+ *
598
+ * @example
599
+ * onExtractionResult={({ toolId, data, status }) => {
600
+ * if (status === 'success') {
601
+ * setFormFields(data);
602
+ * }
603
+ * }}
604
+ */
605
+ onExtractionResult?: (result: {
606
+ fileId: string;
607
+ toolId: string;
608
+ data: Record<string, any>;
609
+ status: 'success' | 'partial' | 'failed';
610
+ incompleteFields?: string[];
611
+ }) => void;
612
+ /** Opt-out of voice chat. Server must also allow it. Default: true */
613
+ voiceEnabled?: boolean;
614
+ /**
615
+ * Custom voice page components
616
+ * Override the default voice indicator, background, or transcript components
617
+ * with your own (e.g. Lottie animation, GIF, custom visuals).
618
+ *
619
+ * @example
620
+ * voiceComponents={{
621
+ * VoiceIndicator: ({ state, amplitude, theme }) => (
622
+ * <LottieView source={lottieFile} speed={amplitude} />
623
+ * ),
624
+ * }}
625
+ */
626
+ voiceComponents?: VoiceComponents;
627
+ /**
628
+ * How the voice mode transcript area is displayed.
629
+ *
630
+ * - `"centered"` (default): single line of the current AI transcript above
631
+ * the state label. Backward-compatible with prior SDK versions.
632
+ * - `"chat"`: scrolling history of user + AI turns. Hands-free friendly:
633
+ * the chat collapses automatically when a tool result is rendered, and
634
+ * resumes when the tool UI is dismissed.
635
+ * - `"off"`: hide transcripts and state label entirely; only the orb shows.
636
+ *
637
+ * @default "centered"
638
+ */
639
+ voiceTranscript?: VoiceTranscriptMode;
640
+ /**
641
+ * Controls how voice tool results accumulate on screen.
642
+ *
643
+ * - `"upsert"` (default): same toolKey replaces its previous entry; results
644
+ * from different tools coexist (e.g. `show_results` + `show_details`
645
+ * stack vertically). Best for browsing scenarios.
646
+ * - `"replace"`: every new tool result clears previous entries — only the
647
+ * latest single tool stays visible. Best for single-focus voice flows
648
+ * where switching tools means switching the whole view.
649
+ *
650
+ * @default "upsert"
651
+ */
652
+ toolRenderMode?: 'upsert' | 'replace';
653
+ /**
654
+ * Callback when close button is pressed
655
+ * If provided, a close button will be shown in the top right corner
656
+ */
657
+ onClose?: () => void;
658
+ /**
659
+ * Callback when back button is pressed
660
+ * If provided, a back button will be shown in the top left corner
661
+ */
662
+ onBack?: () => void;
663
+ /**
664
+ * Custom Close Component
665
+ * Overrides default close button
666
+ */
667
+ CloseComponent?: React.ComponentType<any>;
668
+ /**
669
+ * Custom Back Component
670
+ * Overrides default back button
671
+ */
672
+ BackComponent?: React.ComponentType<any>;
673
+ /**
674
+ * Navigation button label format function
675
+ * Allows customizing the navigation button text format
676
+ * Receives the screen name and returns the formatted label
677
+ *
678
+ * @example
679
+ * // Custom format: "Navigate to Profile"
680
+ * navigationLabelFormat={(screenName) => `Navigate to ${screenName}`}
681
+ *
682
+ * @example
683
+ * // Custom format: "Target screen: Profile"
684
+ * navigationLabelFormat={(screenName) => `Target screen: ${screenName}`}
685
+ */
686
+ navigationLabelFormat?: (screenName: string) => string;
687
+ /**
688
+ * Custom Navigation Button Component
689
+ * Overrides the default navigation button
690
+ * Receives the navigation suggestion data as props
691
+ *
692
+ * @example
693
+ * NavigationButtonComponent={({ screenName, onPress, style }) => (
694
+ * <TouchableOpacity onPress={onPress} style={yourCustomStyle}>
695
+ * <Text>Go to {screenName}</Text>
696
+ * </TouchableOpacity>
697
+ * )}
698
+ */
699
+ NavigationButtonComponent?: React.ComponentType<NavigationButtonProps>;
700
+ }
701
+ /**
702
+ * Props for custom NavigationButtonComponent
703
+ */
704
+ export interface NavigationButtonProps {
705
+ /**
706
+ * The suggested screen name to navigate to
707
+ */
708
+ screenName: string;
709
+ /**
710
+ * Full navigation suggestion data from the backend
711
+ */
712
+ suggestion: NavigationSuggestion;
713
+ /**
714
+ * Callback when button is pressed
715
+ */
716
+ onPress: () => void;
717
+ /**
718
+ * Current theme
719
+ */
720
+ theme: Theme;
721
+ /**
722
+ * Button style variant
723
+ */
724
+ style?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning';
725
+ /**
726
+ * The formatted label text (already formatted by navigationLabelFormat or default)
727
+ */
728
+ label: string;
729
+ /**
730
+ * Optional icon for the button
731
+ */
732
+ icon?: string;
733
+ }
734
+ /**
735
+ * Action button type
736
+ */
737
+ export interface ActionButton {
738
+ id: string;
739
+ type: 'navigation' | 'custom';
740
+ label: string;
741
+ icon?: string;
742
+ data: any;
743
+ style?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning';
744
+ }