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