@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,384 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MessageBubble = MessageBubble;
7
+ const react_1 = __importDefault(require("react"));
8
+ const react_native_1 = require("react-native");
9
+ const MarkdownText_1 = require("./MarkdownText");
10
+ const fontUtils_1 = require("../utils/fontUtils");
11
+ const ActionResultBadge_1 = require("./ActionResultBadge");
12
+ const StepProgressIndicator_1 = require("./StepProgressIndicator");
13
+ const SuggestionButton_1 = require("./SuggestionButton");
14
+ const CardRenderer_1 = require("../cards/renderer/CardRenderer");
15
+ /**
16
+ * MessageBubble Component
17
+ *
18
+ * Displays a chat message bubble with support for:
19
+ * - User and AI messages with different styles
20
+ * - Timestamps
21
+ * - Theming support
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <MessageBubble
26
+ * message="Hello, how can I help?"
27
+ * role="assistant"
28
+ * timestamp={new Date()}
29
+ * theme={lightTheme}
30
+ * showTimestamp
31
+ * />
32
+ * ```
33
+ */
34
+ function MessageBubble({ message, role, timestamp, theme, showTimestamp = true, actions, onActionPress, componentRegistry, toolDefinition, toolResultData, NavigationButtonComponent, actionResults, completedSteps, externalSuggestions, onExternalPress, card, cardHostCallbacks, cardLifecycle, }) {
35
+ const isUser = role === 'user';
36
+ // Render a tool result using a partner-provided custom component
37
+ // (component registry path).
38
+ const renderToolResult = () => {
39
+ const uiConfig = toolDefinition?.uiConfig;
40
+ const response = toolDefinition?.response ?? (uiConfig ? {
41
+ type: uiConfig.responseType,
42
+ dataPath: uiConfig.dataPath,
43
+ itemComponent: uiConfig.itemComponent,
44
+ maxItems: uiConfig.maxItems,
45
+ layout: uiConfig.layout,
46
+ position: uiConfig.position,
47
+ } : undefined);
48
+ const position = response?.position || 'after';
49
+ if (!toolDefinition || !componentRegistry) {
50
+ return null;
51
+ }
52
+ if (!response) {
53
+ return null;
54
+ }
55
+ // uiConfig-only tools may have no data — render component with empty object
56
+ const toolResultDataResolved = toolResultData ?? {};
57
+ const componentName = response.itemComponent;
58
+ if (!componentName || !componentRegistry[componentName]) {
59
+ return null;
60
+ }
61
+ const CustomComponent = componentRegistry[componentName];
62
+ // Extract data from response using dataPath
63
+ let dataToRender = toolResultDataResolved;
64
+ if (response.dataPath) {
65
+ const pathParts = response.dataPath.split('.');
66
+ for (const part of pathParts) {
67
+ dataToRender = dataToRender?.[part];
68
+ }
69
+ }
70
+ // Handle different response types
71
+ if (response.type === 'list' && Array.isArray(dataToRender)) {
72
+ // Limit items if maxItems is set
73
+ const items = response.maxItems
74
+ ? dataToRender.slice(0, response.maxItems)
75
+ : dataToRender;
76
+ // Check layout preference (default: vertical)
77
+ const layout = response.layout || 'vertical';
78
+ // If custom layout, pass entire array to component
79
+ if (layout === 'custom') {
80
+ return (<react_native_1.View style={{ marginTop: theme.spacing.md }}>
81
+ <CustomComponent data={items} // Full array
82
+ tool={toolDefinition} theme={theme}/>
83
+ {response.maxItems && dataToRender.length > response.maxItems && (<react_native_1.Text style={{
84
+ fontSize: theme.typography.fontSize.xs,
85
+ color: theme.colors.textSecondary,
86
+ marginTop: theme.spacing.xs,
87
+ }}>
88
+ ...and {dataToRender.length - response.maxItems} more
89
+ </react_native_1.Text>)}
90
+ </react_native_1.View>);
91
+ }
92
+ // Horizontal scroll layout
93
+ if (layout === 'horizontal') {
94
+ return (<react_native_1.View style={{
95
+ marginTop: theme.spacing.md,
96
+ marginBottom: theme.spacing.md,
97
+ }}>
98
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} style={{
99
+ flexGrow: 0,
100
+ width: react_native_1.Dimensions.get('screen').width,
101
+ marginLeft: -theme.spacing.md,
102
+ }} contentContainerStyle={{
103
+ paddingRight: theme.spacing.md,
104
+ paddingLeft: theme.spacing.md,
105
+ gap: theme.spacing.md,
106
+ }}>
107
+ {items.map((item, index) => (<react_native_1.View key={index} style={{}}>
108
+ <CustomComponent data={item} tool={toolDefinition} theme={theme}/>
109
+ </react_native_1.View>))}
110
+ </react_native_1.ScrollView>
111
+ {response.maxItems && dataToRender.length > response.maxItems && (<react_native_1.Text style={{
112
+ fontSize: theme.typography.fontSize.xs,
113
+ color: theme.colors.textSecondary,
114
+ marginTop: theme.spacing.xs,
115
+ }}>
116
+ ...and {dataToRender.length - response.maxItems} more
117
+ </react_native_1.Text>)}
118
+ </react_native_1.View>);
119
+ }
120
+ // Default vertical layout
121
+ return (<react_native_1.View style={{ marginTop: theme.spacing.md }}>
122
+ {items.map((item, index) => {
123
+ return (<react_native_1.View key={index} style={{ marginBottom: theme.spacing.sm }}>
124
+ <CustomComponent data={item} tool={toolDefinition} theme={theme}/>
125
+ </react_native_1.View>);
126
+ })}
127
+ {response.maxItems && dataToRender.length > response.maxItems && (<react_native_1.Text style={{
128
+ fontSize: theme.typography.fontSize.xs,
129
+ color: theme.colors.textSecondary,
130
+ marginTop: theme.spacing.xs,
131
+ }}>
132
+ ...and {dataToRender.length - response.maxItems} more
133
+ </react_native_1.Text>)}
134
+ </react_native_1.View>);
135
+ }
136
+ else if (response.type === 'card' || response.type === 'detail') {
137
+ // Single item rendering
138
+ return (<react_native_1.View style={{ marginTop: theme.spacing.md }}>
139
+ <CustomComponent data={dataToRender} tool={toolDefinition} theme={theme}/>
140
+ </react_native_1.View>);
141
+ }
142
+ else if (response.type === 'table' && Array.isArray(dataToRender)) {
143
+ // Table rendering (simplified for now)
144
+ return (<react_native_1.View style={{ marginTop: theme.spacing.md }}>
145
+ <CustomComponent data={dataToRender} tool={toolDefinition} theme={theme}/>
146
+ </react_native_1.View>);
147
+ }
148
+ return null;
149
+ };
150
+ // Format timestamp
151
+ const formatTime = (date) => {
152
+ const hours = date.getHours().toString().padStart(2, '0');
153
+ const minutes = date.getMinutes().toString().padStart(2, '0');
154
+ return `${hours}:${minutes}`;
155
+ };
156
+ const containerStyle = {
157
+ alignSelf: isUser ? 'flex-end' : 'flex-start',
158
+ maxWidth: isUser
159
+ ? `${theme.messages?.user.maxWidth || 85}%`
160
+ : `${theme.messages?.ai.maxWidth || 85}%`,
161
+ marginBottom: theme.spacing.sm,
162
+ };
163
+ const bubbleStyle = {
164
+ backgroundColor: isUser ? theme.colors.userBubble : theme.colors.aiBubble,
165
+ borderRadius: theme.borderRadius.lg,
166
+ paddingHorizontal: isUser
167
+ ? theme.messages?.user.padding || theme.spacing.md
168
+ : theme.messages?.ai.padding || theme.spacing.md,
169
+ paddingVertical: theme.spacing.sm + 2,
170
+ };
171
+ const messageTextStyle = {
172
+ color: isUser ? theme.colors.userBubbleText : theme.colors.aiBubbleText,
173
+ fontSize: theme.typography.fontSize.md,
174
+ lineHeight: theme.typography.fontSize.md * theme.typography.lineHeight.normal,
175
+ fontWeight: theme.typography.fontWeight.regular,
176
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'regular'),
177
+ };
178
+ const timestampStyle = {
179
+ fontSize: theme.typography.fontSize.xs,
180
+ color: theme.colors.textSecondary,
181
+ marginTop: theme.spacing.xs,
182
+ alignSelf: isUser ? 'flex-end' : 'flex-start',
183
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'regular'),
184
+ };
185
+ // Check if this is a tool response message (no text, only tool data)
186
+ const isToolResponseOnly = toolDefinition && !message.trim();
187
+ // Check position preference for tool result (default: after).
188
+ // Cards honor the same position preference as the tool's existing uiConfig
189
+ // so partners control placement from one place.
190
+ const uiConfigForPosition = toolDefinition?.uiConfig;
191
+ const toolPosition = toolDefinition?.response?.position || uiConfigForPosition?.position || 'after';
192
+ // Card render block (full-width, breaks out of bubble maxWidth).
193
+ // Honors uiConfig.responseType / dataPath / maxItems / layout the same way the
194
+ // app-component renderer path does — same outer shape, only the leaf renderer differs.
195
+ const renderCard = () => {
196
+ if (isUser || !card) {
197
+ return null;
198
+ }
199
+ const uiConfigForCard = toolDefinition?.uiConfig;
200
+ const responseType = uiConfigForCard?.responseType ?? toolDefinition?.response?.type ?? 'detail';
201
+ const dataPath = uiConfigForCard?.dataPath ?? toolDefinition?.response?.dataPath;
202
+ const maxItems = uiConfigForCard?.maxItems ?? toolDefinition?.response?.maxItems ?? 10;
203
+ const layout = uiConfigForCard?.layout ?? toolDefinition?.response?.layout ?? 'vertical';
204
+ // Resolve dataPath against the full envelope payload.
205
+ let scoped = card.data;
206
+ if (dataPath && typeof dataPath === 'string') {
207
+ for (const part of dataPath.split('.')) {
208
+ scoped = scoped?.[part];
209
+ if (scoped == null)
210
+ break;
211
+ }
212
+ }
213
+ // Determine items: list mode iterates the resolved array; everything else
214
+ // renders a single card with the scoped (or full) data.
215
+ const isListMode = responseType === 'list' && Array.isArray(scoped);
216
+ const items = isListMode
217
+ ? scoped.slice(0, maxItems)
218
+ : [scoped ?? card.data];
219
+ const overflowCount = isListMode
220
+ ? Math.max(0, scoped.length - items.length)
221
+ : 0;
222
+ if (items.length === 0) {
223
+ if (__DEV__) {
224
+ console.warn('[Qafka.cards] renderCard returning null — no items to render', {
225
+ isListMode,
226
+ scopedKind: Array.isArray(scoped) ? 'array' : typeof scoped,
227
+ scopedLength: Array.isArray(scoped) ? scoped.length : 'n/a',
228
+ });
229
+ }
230
+ return null;
231
+ }
232
+ // Pass iterationIndex only when list-mode; CardRenderer threads it down
233
+ // to QButton so CTA telemetry events know which row was clicked. In
234
+ // detail/single mode, iterationIndex stays undefined so partners don't
235
+ // see a misleading `0` in their analytics.
236
+ const renderOne = (data, key) => (<CardRenderer_1.CardRenderer key={key} definition={card.definition} data={data} cardTemplateId={card.templateId} cardSlug={card.templateSlug} hostCallbacks={cardHostCallbacks} lifecycle={cardLifecycle} iterationIndex={isListMode ? key : undefined}/>);
237
+ const overflowLabel = overflowCount > 0 && (<react_native_1.Text style={{
238
+ fontSize: theme.typography.fontSize.xs,
239
+ color: theme.colors.textSecondary,
240
+ fontStyle: 'italic',
241
+ marginTop: theme.spacing.xs,
242
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'regular'),
243
+ }}>
244
+ …and {overflowCount} more
245
+ </react_native_1.Text>);
246
+ if (items.length === 1) {
247
+ return (<react_native_1.View style={{ marginTop: theme.spacing.sm, alignSelf: 'stretch' }}>
248
+ {renderOne(items[0], 0)}
249
+ {overflowLabel}
250
+ </react_native_1.View>);
251
+ }
252
+ if (layout === 'horizontal') {
253
+ // Width policy: respect an explicit width on the card definition's root
254
+ // QView style (partner control). Fall back to 280 so default horizontal
255
+ // lists keep a sensible card width.
256
+ const rootStyle = card.definition?.root?.style;
257
+ const explicitWidth = rootStyle && typeof rootStyle.width === 'number'
258
+ ? rootStyle.width
259
+ : undefined;
260
+ return (<react_native_1.View style={{ marginTop: theme.spacing.sm, alignSelf: 'stretch' }}>
261
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingRight: theme.spacing.md }}>
262
+ {items.map((it, i) => (<react_native_1.View key={i} style={{
263
+ marginRight: theme.spacing.sm,
264
+ width: explicitWidth ?? 280,
265
+ }}>
266
+ {renderOne(it, i)}
267
+ </react_native_1.View>))}
268
+ </react_native_1.ScrollView>
269
+ {overflowLabel}
270
+ </react_native_1.View>);
271
+ }
272
+ return (<react_native_1.View style={{
273
+ marginTop: theme.spacing.sm,
274
+ alignSelf: 'stretch',
275
+ gap: theme.spacing.sm,
276
+ }}>
277
+ {items.map(renderOne)}
278
+ {overflowLabel}
279
+ </react_native_1.View>);
280
+ };
281
+ return (<react_native_1.View>
282
+ <react_native_1.View style={containerStyle}>
283
+ {/* Card / Tool Result BEFORE Message - if position is "before" */}
284
+ {!isUser && toolPosition === 'before' && !isToolResponseOnly && (card ? renderCard() : renderToolResult())}
285
+
286
+ {/* Only show bubble if there's actual message text OR actions OR external suggestions */}
287
+ {(!isToolResponseOnly ||
288
+ (actions && actions.length > 0) ||
289
+ (externalSuggestions && externalSuggestions.length > 0)) && (<react_native_1.View style={bubbleStyle}>
290
+ {message.trim() && (<MarkdownText_1.MarkdownText style={messageTextStyle} theme={theme} isUserMessage={isUser}>
291
+ {message}
292
+ </MarkdownText_1.MarkdownText>)}
293
+
294
+ {/* Action Result Badges */}
295
+ {!isUser && actionResults && actionResults.length > 0 && (<ActionResultBadge_1.ActionResultBadge results={actionResults} theme={theme}/>)}
296
+
297
+ {/* Step Progress Indicator */}
298
+ {!isUser && completedSteps && completedSteps.length > 0 && (<StepProgressIndicator_1.StepProgressIndicator steps={completedSteps} theme={theme}/>)}
299
+
300
+ {/* Action Buttons - Inside bubble, after metadata */}
301
+ {!isUser && actions && actions.length > 0 && (<react_native_1.View style={{ marginTop: theme.spacing.md, gap: theme.spacing.xs }}>
302
+ {actions.map((action) => {
303
+ // Use custom NavigationButtonComponent for navigation actions
304
+ if (action.type === 'navigation' && NavigationButtonComponent) {
305
+ return (<NavigationButtonComponent key={action.id} screenName={action.data?.screenName || ''} suggestion={action.data} onPress={() => onActionPress?.(action)} theme={theme} style={action.style} label={action.label} icon={action.icon}/>);
306
+ }
307
+ // Default button rendering for other action types or when no custom component
308
+ const getButtonColor = () => {
309
+ switch (action.style) {
310
+ case 'primary':
311
+ return theme.colors.primary;
312
+ case 'success':
313
+ return '#10b981'; // green
314
+ case 'danger':
315
+ return '#ef4444'; // red
316
+ case 'warning':
317
+ return '#f59e0b'; // orange
318
+ case 'secondary':
319
+ default:
320
+ return theme.colors.textSecondary;
321
+ }
322
+ };
323
+ const actionButtonStyle = {
324
+ flexDirection: 'row',
325
+ alignItems: 'center',
326
+ justifyContent: 'center',
327
+ backgroundColor: getButtonColor(),
328
+ paddingHorizontal: theme.spacing.md,
329
+ paddingVertical: theme.spacing.sm,
330
+ borderRadius: theme.borderRadius.md,
331
+ marginTop: theme.spacing.xs,
332
+ ...theme.shadows.small,
333
+ };
334
+ const actionButtonTextStyle = {
335
+ color: '#ffffff',
336
+ fontSize: theme.typography.fontSize.sm,
337
+ fontWeight: theme.typography.fontWeight.medium,
338
+ marginLeft: action.icon ? theme.spacing.xs : 0,
339
+ fontFamily: (0, fontUtils_1.getFontFamily)(theme, 'medium'),
340
+ };
341
+ return (<react_native_1.TouchableOpacity key={action.id} style={actionButtonStyle} onPress={() => {
342
+ if (onActionPress) {
343
+ onActionPress(action);
344
+ }
345
+ else if (action.type === 'navigation') {
346
+ try {
347
+ const { router } = require('expo-router');
348
+ const raw = action.data?.route || action.data?.screenName || '';
349
+ const route = raw.startsWith('/') ? raw : `/${raw}`;
350
+ router.push(route);
351
+ }
352
+ catch { /* no-op */ }
353
+ }
354
+ }} activeOpacity={0.7}>
355
+ {action.icon && (<react_native_1.Text style={{ fontSize: 16 }}>{action.icon}</react_native_1.Text>)}
356
+ <react_native_1.Text style={actionButtonTextStyle}>{action.label}</react_native_1.Text>
357
+ </react_native_1.TouchableOpacity>);
358
+ })}
359
+ </react_native_1.View>)}
360
+
361
+ {/* External Suggestion Buttons (WhatsApp, phone, map, …) */}
362
+ {!isUser &&
363
+ externalSuggestions &&
364
+ externalSuggestions.length > 0 && (<react_native_1.View style={{
365
+ marginTop: theme.spacing.md,
366
+ gap: theme.spacing.xs,
367
+ }}>
368
+ {externalSuggestions.map((ext) => (<SuggestionButton_1.SuggestionButton key={ext.id} suggestion={{ kind: 'external', data: ext }} onPress={() => onExternalPress?.(ext)} theme={theme}/>))}
369
+ </react_native_1.View>)}
370
+ </react_native_1.View>)}
371
+
372
+ {/* Tool Result Rendering:
373
+ * - If tool response only (no message text): always show the tool
374
+ * - If there's a message: show tool AFTER message if position is "after" (default)
375
+ */}
376
+ </react_native_1.View>
377
+
378
+ {/* AFTER position (default): card replaces tool result if both apply */}
379
+ {!isUser && (isToolResponseOnly || toolPosition === 'after') && (card ? renderCard() : renderToolResult())}
380
+
381
+ {/* Timestamp */}
382
+ {showTimestamp && (<react_native_1.Text style={timestampStyle}>{formatTime(timestamp)}</react_native_1.Text>)}
383
+ </react_native_1.View>);
384
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { Theme } from '../themes';
3
+ interface NavigationSuggestionCardProps {
4
+ screenName: string;
5
+ message?: string;
6
+ onAccept: () => void;
7
+ onDismiss: () => void;
8
+ theme: Theme;
9
+ }
10
+ export declare function NavigationSuggestionCard({ screenName, message, onAccept, onDismiss, theme, }: NavigationSuggestionCardProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NavigationSuggestionCard = NavigationSuggestionCard;
7
+ const react_1 = __importDefault(require("react"));
8
+ const react_native_1 = require("react-native");
9
+ function NavigationSuggestionCard({ screenName, message, onAccept, onDismiss, theme, }) {
10
+ return (<react_native_1.View style={[
11
+ styles.container,
12
+ {
13
+ backgroundColor: theme.colors.surface,
14
+ borderColor: theme.colors.primary,
15
+ },
16
+ ]}>
17
+ {/* Icon */}
18
+ <react_native_1.View style={styles.iconContainer}>
19
+ <react_native_1.Text style={styles.icon}>🧭</react_native_1.Text>
20
+ </react_native_1.View>
21
+
22
+ {/* Content */}
23
+ <react_native_1.View style={styles.content}>
24
+ <react_native_1.Text style={[styles.title, { color: theme.colors.text }]}>
25
+ Navigate to {screenName}
26
+ </react_native_1.Text>
27
+ {message && (<react_native_1.Text style={[styles.message, { color: theme.colors.textSecondary }]} numberOfLines={2}>
28
+ {message}
29
+ </react_native_1.Text>)}
30
+ </react_native_1.View>
31
+
32
+ {/* Actions */}
33
+ <react_native_1.View style={styles.actions}>
34
+ <react_native_1.TouchableOpacity style={[
35
+ styles.button,
36
+ styles.dismissButton,
37
+ { borderColor: theme.colors.border || '#E0E0E0' },
38
+ ]} onPress={onDismiss} activeOpacity={0.7}>
39
+ <react_native_1.Text style={[styles.buttonText, { color: theme.colors.textSecondary }]}>
40
+ Dismiss
41
+ </react_native_1.Text>
42
+ </react_native_1.TouchableOpacity>
43
+
44
+ <react_native_1.TouchableOpacity style={[
45
+ styles.button,
46
+ styles.acceptButton,
47
+ { backgroundColor: theme.colors.primary },
48
+ ]} onPress={onAccept} activeOpacity={0.8}>
49
+ <react_native_1.Text style={[styles.buttonText, { color: '#FFFFFF' }]}>Go</react_native_1.Text>
50
+ </react_native_1.TouchableOpacity>
51
+ </react_native_1.View>
52
+ </react_native_1.View>);
53
+ }
54
+ const styles = react_native_1.StyleSheet.create({
55
+ container: {
56
+ flexDirection: 'row',
57
+ padding: 12,
58
+ borderRadius: 12,
59
+ borderWidth: 1,
60
+ marginHorizontal: 16,
61
+ marginVertical: 8,
62
+ alignItems: 'center',
63
+ },
64
+ iconContainer: {
65
+ width: 40,
66
+ height: 40,
67
+ borderRadius: 20,
68
+ justifyContent: 'center',
69
+ alignItems: 'center',
70
+ marginRight: 12,
71
+ },
72
+ icon: {
73
+ fontSize: 24,
74
+ },
75
+ content: {
76
+ flex: 1,
77
+ marginRight: 8,
78
+ },
79
+ title: {
80
+ fontSize: 15,
81
+ fontWeight: '600',
82
+ marginBottom: 4,
83
+ },
84
+ message: {
85
+ fontSize: 13,
86
+ marginBottom: 2,
87
+ },
88
+ actions: {
89
+ flexDirection: 'row',
90
+ gap: 8,
91
+ },
92
+ button: {
93
+ paddingHorizontal: 16,
94
+ paddingVertical: 8,
95
+ borderRadius: 16,
96
+ minWidth: 60,
97
+ alignItems: 'center',
98
+ },
99
+ dismissButton: {
100
+ borderWidth: 1,
101
+ },
102
+ acceptButton: {
103
+ // backgroundColor set from theme
104
+ },
105
+ buttonText: {
106
+ fontSize: 14,
107
+ fontWeight: '600',
108
+ },
109
+ });
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { QafkaProps, QafkaHandle } from './Qafka.types';
3
+ /**
4
+ * Qafka Component
5
+ *
6
+ * Main chat widget component with full UI and functionality.
7
+ *
8
+ * Features:
9
+ * - Three display modes: fullscreen, inline, floating
10
+ * - Complete chat UI with message list and input
11
+ * - Integration with QafkaSDK for backend communication
12
+ * - Auto-loading theme from API
13
+ * - Theme support (light/dark) with overrides
14
+ * - Typing indicators and streaming responses
15
+ * - Tool Registry integration
16
+ * - Message metadata display
17
+ * - Voice chat mode (swipe to access)
18
+ *
19
+ * @example Basic usage
20
+ * ```tsx
21
+ * <Qafka
22
+ * mode="fullscreen"
23
+ * theme="light"
24
+ * />
25
+ * ```
26
+ *
27
+ * @example With theme override
28
+ * ```tsx
29
+ * <Qafka
30
+ * themeOverride={{
31
+ * colors: {
32
+ * userBubble: '#FF0000',
33
+ * userBubbleText: '#FFFFFF'
34
+ * }
35
+ * }}
36
+ * />
37
+ * ```
38
+ */
39
+ export declare const Qafka: React.ForwardRefExoticComponent<QafkaProps & React.RefAttributes<QafkaHandle>>;
@@ -0,0 +1,21 @@
1
+ import { ChatMessage } from '../types/chat';
2
+ import { ExternalSuggestion } from '../types/external-navigation';
3
+ import { NavigationSuggestion } from '../types/navigation';
4
+ /**
5
+ * Default external suggestion handler.
6
+ *
7
+ * Tries to open the primary URL via `Linking.openURL`. Falls back to
8
+ * `fallbackUrl` when `canOpenURL` reports the URL is not handleable by the
9
+ * device (e.g. the target app isn't installed). Errors are swallowed — the
10
+ * host app can observe failures by providing its own `onExternalSuggestion`
11
+ * callback.
12
+ */
13
+ export declare function handleExternalSuggestionDefault(s: ExternalSuggestion): Promise<void>;
14
+ export interface ActionHandlerOptions {
15
+ onNavigationAction?: (suggestion: NavigationSuggestion) => void;
16
+ setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
17
+ }
18
+ /**
19
+ * Handle action button clicks (navigation only).
20
+ */
21
+ export declare const createActionHandler: (options: ActionHandlerOptions) => (action: any) => void;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createActionHandler = void 0;
4
+ exports.handleExternalSuggestionDefault = handleExternalSuggestionDefault;
5
+ const react_native_1 = require("react-native");
6
+ /**
7
+ * Default external suggestion handler.
8
+ *
9
+ * Tries to open the primary URL via `Linking.openURL`. Falls back to
10
+ * `fallbackUrl` when `canOpenURL` reports the URL is not handleable by the
11
+ * device (e.g. the target app isn't installed). Errors are swallowed — the
12
+ * host app can observe failures by providing its own `onExternalSuggestion`
13
+ * callback.
14
+ */
15
+ async function handleExternalSuggestionDefault(s) {
16
+ try {
17
+ const canOpen = await react_native_1.Linking.canOpenURL(s.url);
18
+ if (canOpen) {
19
+ await react_native_1.Linking.openURL(s.url);
20
+ return;
21
+ }
22
+ if (s.fallbackUrl) {
23
+ await react_native_1.Linking.openURL(s.fallbackUrl);
24
+ }
25
+ }
26
+ catch {
27
+ // silently swallow — consumer may log via callback
28
+ }
29
+ }
30
+ const normalizeRoute = (route) => route.startsWith('/') ? route : `/${route}`;
31
+ /**
32
+ * Handle action button clicks (navigation only).
33
+ */
34
+ const createActionHandler = (options) => {
35
+ const { onNavigationAction } = options;
36
+ return (action) => {
37
+ if (action.type === 'navigation') {
38
+ const suggestion = action.data;
39
+ if (onNavigationAction) {
40
+ onNavigationAction(suggestion);
41
+ return;
42
+ }
43
+ try {
44
+ const { router } = require('expo-router');
45
+ const route = normalizeRoute(suggestion.route || suggestion.screenName);
46
+ router.push(route);
47
+ }
48
+ catch {
49
+ // expo-router not available, no-op
50
+ }
51
+ }
52
+ };
53
+ };
54
+ exports.createActionHandler = createActionHandler;