@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.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +92 -0
- package/LICENSE +22 -0
- package/README.md +109 -0
- package/SECURITY.md +67 -0
- package/android/build.gradle +35 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/qafka/attestation/QafkaAttestationModule.kt +92 -0
- package/android/src/main/java/com/qafka/attestation/QafkaAttestationPackage.kt +22 -0
- package/android/src/main/java/com/qafka/audio/QafkaAudioModule.kt +290 -0
- package/android/src/main/java/com/qafka/clipboard/QafkaClipboardModule.kt +28 -0
- package/android/src/main/java/com/qafka/storage/QafkaStorageModule.kt +80 -0
- package/app.plugin.js +1 -0
- package/dist/QafkaSDK.d.ts +174 -0
- package/dist/QafkaSDK.js +461 -0
- package/dist/cards/bindings/resolveFieldName.d.ts +25 -0
- package/dist/cards/bindings/resolveFieldName.js +82 -0
- package/dist/cards/cta/CardContext.d.ts +16 -0
- package/dist/cards/cta/CardContext.js +58 -0
- package/dist/cards/cta/dispatcher.d.ts +7 -0
- package/dist/cards/cta/dispatcher.js +90 -0
- package/dist/cards/cta/types.d.ts +66 -0
- package/dist/cards/cta/types.js +2 -0
- package/dist/cards/index.d.ts +20 -0
- package/dist/cards/index.js +34 -0
- package/dist/cards/primitives/QButton.d.ts +10 -0
- package/dist/cards/primitives/QButton.js +115 -0
- package/dist/cards/primitives/QDivider.d.ts +7 -0
- package/dist/cards/primitives/QDivider.js +17 -0
- package/dist/cards/primitives/QIcon.d.ts +13 -0
- package/dist/cards/primitives/QIcon.js +26 -0
- package/dist/cards/primitives/QImage.d.ts +9 -0
- package/dist/cards/primitives/QImage.js +22 -0
- package/dist/cards/primitives/QText.d.ts +9 -0
- package/dist/cards/primitives/QText.js +30 -0
- package/dist/cards/primitives/QView.d.ts +8 -0
- package/dist/cards/primitives/QView.js +19 -0
- package/dist/cards/renderer/CardRenderer.d.ts +19 -0
- package/dist/cards/renderer/CardRenderer.js +64 -0
- package/dist/cards/renderer/renderNode.d.ts +13 -0
- package/dist/cards/renderer/renderNode.js +42 -0
- package/dist/cards/types.d.ts +110 -0
- package/dist/cards/types.js +6 -0
- package/dist/components/ActionResultBadge.d.ts +12 -0
- package/dist/components/ActionResultBadge.js +58 -0
- package/dist/components/ChatPage.d.ts +44 -0
- package/dist/components/ChatPage.js +84 -0
- package/dist/components/DataChip.d.ts +8 -0
- package/dist/components/DataChip.js +80 -0
- package/dist/components/DataChipList.d.ts +13 -0
- package/dist/components/DataChipList.js +21 -0
- package/dist/components/FloatingButton.d.ts +11 -0
- package/dist/components/FloatingButton.js +162 -0
- package/dist/components/InputArea.d.ts +57 -0
- package/dist/components/InputArea.js +142 -0
- package/dist/components/MarkdownText.d.ts +15 -0
- package/dist/components/MarkdownText.js +283 -0
- package/dist/components/MessageBubble.d.ts +134 -0
- package/dist/components/MessageBubble.js +384 -0
- package/dist/components/NavigationSuggestion.d.ts +11 -0
- package/dist/components/NavigationSuggestion.js +109 -0
- package/dist/components/Qafka.d.ts +39 -0
- package/dist/components/Qafka.handlers.d.ts +21 -0
- package/dist/components/Qafka.handlers.js +54 -0
- package/dist/components/Qafka.js +493 -0
- package/dist/components/Qafka.styles.d.ts +19 -0
- package/dist/components/Qafka.styles.js +101 -0
- package/dist/components/Qafka.types.d.ts +744 -0
- package/dist/components/Qafka.types.js +2 -0
- package/dist/components/Qafka.utils.d.ts +7 -0
- package/dist/components/Qafka.utils.js +34 -0
- package/dist/components/QafkaProvider.d.ts +12 -0
- package/dist/components/QafkaProvider.js +87 -0
- package/dist/components/QuickReplies.d.ts +14 -0
- package/dist/components/QuickReplies.js +48 -0
- package/dist/components/StepProgressIndicator.d.ts +12 -0
- package/dist/components/StepProgressIndicator.js +48 -0
- package/dist/components/SuggestionButton.d.ts +42 -0
- package/dist/components/SuggestionButton.js +67 -0
- package/dist/components/ToolStatusPill.d.ts +20 -0
- package/dist/components/ToolStatusPill.js +43 -0
- package/dist/components/TypingIndicator.d.ts +28 -0
- package/dist/components/TypingIndicator.js +109 -0
- package/dist/components/VoicePage.d.ts +48 -0
- package/dist/components/VoicePage.js +683 -0
- package/dist/components/defaults/DefaultCard.d.ts +14 -0
- package/dist/components/defaults/DefaultCard.js +156 -0
- package/dist/components/defaults/DefaultDetail.d.ts +14 -0
- package/dist/components/defaults/DefaultDetail.js +138 -0
- package/dist/components/defaults/DefaultList.d.ts +12 -0
- package/dist/components/defaults/DefaultList.js +98 -0
- package/dist/components/defaults/DefaultTable.d.ts +14 -0
- package/dist/components/defaults/DefaultTable.js +204 -0
- package/dist/components/defaults/index.d.ts +14 -0
- package/dist/components/defaults/index.js +25 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.js +36 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +13 -0
- package/dist/hooks/useChatMessages.d.ts +72 -0
- package/dist/hooks/useChatMessages.js +505 -0
- package/dist/hooks/useContextManager.d.ts +12 -0
- package/dist/hooks/useContextManager.js +46 -0
- package/dist/hooks/useProjectTheme.d.ts +19 -0
- package/dist/hooks/useProjectTheme.js +163 -0
- package/dist/hooks/useSDK.d.ts +31 -0
- package/dist/hooks/useSDK.js +103 -0
- package/dist/hooks/useVoiceChat.d.ts +110 -0
- package/dist/hooks/useVoiceChat.js +436 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +59 -0
- package/dist/native/QafkaAttestation.d.ts +23 -0
- package/dist/native/QafkaAttestation.js +70 -0
- package/dist/native/QafkaAudio.d.ts +14 -0
- package/dist/native/QafkaAudio.js +31 -0
- package/dist/native/QafkaClipboard.d.ts +11 -0
- package/dist/native/QafkaClipboard.js +14 -0
- package/dist/native/QafkaStorage.d.ts +15 -0
- package/dist/native/QafkaStorage.js +12 -0
- package/dist/resolve-project-config.d.ts +35 -0
- package/dist/resolve-project-config.js +41 -0
- package/dist/runtime-config-loader.d.ts +37 -0
- package/dist/runtime-config-loader.js +53 -0
- package/dist/services/AttestationManager.d.ts +38 -0
- package/dist/services/AttestationManager.js +296 -0
- package/dist/services/BackendService.d.ts +156 -0
- package/dist/services/BackendService.js +755 -0
- package/dist/services/ConversationManager.d.ts +43 -0
- package/dist/services/ConversationManager.js +96 -0
- package/dist/services/NavigationHandler.d.ts +29 -0
- package/dist/services/NavigationHandler.js +70 -0
- package/dist/services/RealtimeService.d.ts +83 -0
- package/dist/services/RealtimeService.js +203 -0
- package/dist/services/storage.d.ts +11 -0
- package/dist/services/storage.js +15 -0
- package/dist/services/storageCore.d.ts +17 -0
- package/dist/services/storageCore.js +46 -0
- package/dist/themes/dark.d.ts +5 -0
- package/dist/themes/dark.js +129 -0
- package/dist/themes/index.d.ts +12 -0
- package/dist/themes/index.js +33 -0
- package/dist/themes/light.d.ts +5 -0
- package/dist/themes/light.js +129 -0
- package/dist/themes/types.d.ts +155 -0
- package/dist/themes/types.js +5 -0
- package/dist/types/chat.d.ts +126 -0
- package/dist/types/chat.js +5 -0
- package/dist/types/components.d.ts +56 -0
- package/dist/types/components.js +16 -0
- package/dist/types/external-navigation.d.ts +19 -0
- package/dist/types/external-navigation.js +8 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +25 -0
- package/dist/types/navigation.d.ts +86 -0
- package/dist/types/navigation.js +5 -0
- package/dist/types/sdk.d.ts +36 -0
- package/dist/types/sdk.js +5 -0
- package/dist/utils/deepMerge.d.ts +46 -0
- package/dist/utils/deepMerge.js +70 -0
- package/dist/utils/fontUtils.d.ts +8 -0
- package/dist/utils/fontUtils.js +16 -0
- package/dist/validate-end-user.d.ts +18 -0
- package/dist/validate-end-user.js +74 -0
- package/expo-plugin/withQafkaAttestation.js +57 -0
- package/ios/QafkaAttestation.m +25 -0
- package/ios/QafkaAttestation.swift +128 -0
- package/ios/QafkaAudio.m +23 -0
- package/ios/QafkaAudio.swift +519 -0
- package/ios/QafkaClipboard.m +10 -0
- package/ios/QafkaClipboard.swift +21 -0
- package/ios/QafkaReactImports.h +2 -0
- package/ios/QafkaStorage.m +26 -0
- package/ios/QafkaStorage.swift +118 -0
- package/package.json +82 -0
- package/qafka.config.d.ts +9 -0
- package/qafka.config.js +9 -0
- package/react-native-qafka.podspec +28 -0
- 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;
|