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