@worktual/react-native-ai-bot 1.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/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @worktual/react-native-ai-bot
2
+
3
+ Embeddable AI chatbot for React Native applications. Add a fully-featured AI assistant to your app with just a few lines of code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @worktual/react-native-ai-bot
9
+ ```
10
+
11
+ ### Peer Dependencies
12
+
13
+ Make sure these are installed in your project:
14
+
15
+ ```bash
16
+ npm install react-native-webview react-native-safe-area-context
17
+ ```
18
+
19
+ For iOS:
20
+ ```bash
21
+ cd ios && pod install
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```tsx
27
+ import React from "react";
28
+ import { WorktualAIBot } from "@worktual/react-native-ai-bot";
29
+
30
+ const ChatBotScreen = ({ navigation }) => {
31
+ return (
32
+ <WorktualAIBot
33
+ webchatId="YOUR_WEBCHAT_ID"
34
+ onClose={() => navigation.goBack()}
35
+ />
36
+ );
37
+ };
38
+
39
+ export default ChatBotScreen;
40
+ ```
41
+
42
+ Register it as a screen in your navigator:
43
+
44
+ ```tsx
45
+ <Stack.Screen name="ChatBot" component={ChatBotScreen} />
46
+ ```
47
+
48
+ Then navigate to it from anywhere (e.g. a floating button):
49
+
50
+ ```tsx
51
+ <TouchableOpacity onPress={() => navigation.navigate("ChatBot")}>
52
+ <Text>Open AI Chat</Text>
53
+ </TouchableOpacity>
54
+ ```
55
+
56
+ ## Props
57
+
58
+ | Prop | Type | Required | Default | Description |
59
+ |------|------|----------|---------|-------------|
60
+ | `webchatId` | `string` | Yes | — | Your unique webchat ID provided by Worktual |
61
+ | `onClose` | `() => void` | Yes | — | Called when the chat ends (navigate back / dismiss) |
62
+ | `baseUrl` | `string` | No | Worktual production URL | Custom bot URL if self-hosted |
63
+ | `loadingTitle` | `string` | No | `"AI Assistant"` | Title shown on loading screen |
64
+ | `loadingSubtitle` | `string` | No | `"Loading your chat..."` | Subtitle shown on loading screen |
65
+ | `primaryColor` | `string` | No | `"#575CFF"` | Colour for progress bar & spinner |
66
+ | `loadingBackground` | `string` | No | `"#f8f9fb"` | Loading screen background colour |
67
+ | `maxLoadTime` | `number` | No | `6000` | Max ms to wait before force-showing chat |
68
+ | `onReady` | `() => void` | No | — | Called when chat content is fully loaded |
69
+ | `onMessage` | `(data) => void` | No | — | Called on every message from the chat |
70
+
71
+ ## Customisation Examples
72
+
73
+ ### Custom branding
74
+
75
+ ```tsx
76
+ <WorktualAIBot
77
+ webchatId="YOUR_WEBCHAT_ID"
78
+ onClose={() => navigation.goBack()}
79
+ loadingTitle="Support Chat"
80
+ loadingSubtitle="Connecting to an agent..."
81
+ primaryColor="#FF6B00"
82
+ loadingBackground="#FFF8F0"
83
+ />
84
+ ```
85
+
86
+ ### Inside a Modal
87
+
88
+ ```tsx
89
+ import { Modal } from "react-native";
90
+
91
+ const App = () => {
92
+ const [showBot, setShowBot] = useState(false);
93
+
94
+ return (
95
+ <>
96
+ <Button title="Chat" onPress={() => setShowBot(true)} />
97
+ <Modal visible={showBot} animationType="slide">
98
+ <WorktualAIBot
99
+ webchatId="YOUR_WEBCHAT_ID"
100
+ onClose={() => setShowBot(false)}
101
+ />
102
+ </Modal>
103
+ </>
104
+ );
105
+ };
106
+ ```
107
+
108
+ ### Listen for events
109
+
110
+ ```tsx
111
+ <WorktualAIBot
112
+ webchatId="YOUR_WEBCHAT_ID"
113
+ onClose={() => navigation.goBack()}
114
+ onReady={() => console.log("Bot is ready!")}
115
+ onMessage={(data) => console.log("Bot event:", data)}
116
+ />
117
+ ```
118
+
119
+ ## How It Works
120
+
121
+ 1. Client taps a button → navigates to the bot screen
122
+ 2. A branded loading screen appears (with animated progress bar)
123
+ 3. The chat loads behind the loading screen
124
+ 4. Once chat content is rendered, the loader fades out smoothly
125
+ 5. When the user closes the chat (via the bot's close button), `onClose` is called
126
+
127
+ ## Requirements
128
+
129
+ - React Native >= 0.65
130
+ - react-native-webview >= 11.0
131
+ - react-native-safe-area-context >= 4.0
132
+ - iOS 13+ / Android 5+
133
+
134
+ ## Support
135
+
136
+ Contact your Worktual account manager for your `webchatId` and integration support.
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@worktual/react-native-ai-bot",
3
+ "version": "1.0.0",
4
+ "description": "Worktual AI Bot - Embeddable AI chatbot for React Native apps",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "react-native",
13
+ "ai",
14
+ "chatbot",
15
+ "worktual",
16
+ "webchat"
17
+ ],
18
+ "author": "Worktual",
19
+ "license": "MIT",
20
+ "peerDependencies": {
21
+ "react": ">=17.0.0",
22
+ "react-native": ">=0.65.0",
23
+ "react-native-safe-area-context": ">=4.0.0",
24
+ "react-native-webview": ">=11.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "*",
28
+ "@types/react-native": "*",
29
+ "typescript": "^5.9.3"
30
+ },
31
+ "files": [
32
+ "src/",
33
+ "README.md"
34
+ ]
35
+ }
@@ -0,0 +1,293 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from "react";
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Animated,
6
+ Text,
7
+ ActivityIndicator,
8
+ StatusBar,
9
+ Platform,
10
+ } from "react-native";
11
+ import { WebView, WebViewMessageEvent } from "react-native-webview";
12
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
13
+
14
+ /* ────────────────────────────────────────────────────────
15
+ * Public types
16
+ * ──────────────────────────────────────────────────────── */
17
+
18
+ export interface WorktualAIBotProps {
19
+ /** Your unique webchat ID provided by Worktual */
20
+ webchatId: string;
21
+
22
+ /** Called when the bot sends a close/end event (navigate back, dismiss modal, etc.) */
23
+ onClose: () => void;
24
+
25
+ /** Optional: custom base URL (defaults to Worktual production) */
26
+ baseUrl?: string;
27
+
28
+ /** Optional: loading screen title (default: "AI Assistant") */
29
+ loadingTitle?: string;
30
+
31
+ /** Optional: loading screen subtitle (default: "Loading your chat...") */
32
+ loadingSubtitle?: string;
33
+
34
+ /** Optional: primary colour for progress bar & spinner (default: "#575CFF") */
35
+ primaryColor?: string;
36
+
37
+ /** Optional: loading screen background colour (default: "#f8f9fb") */
38
+ loadingBackground?: string;
39
+
40
+ /** Optional: maximum time (ms) to wait before force-hiding loader (default: 6000) */
41
+ maxLoadTime?: number;
42
+
43
+ /** Optional: called when bot content is fully loaded */
44
+ onReady?: () => void;
45
+
46
+ /** Optional: called on every postMessage from the WebView */
47
+ onMessage?: (data: Record<string, unknown>) => void;
48
+ }
49
+
50
+ /* ────────────────────────────────────────────────────────
51
+ * Constants
52
+ * ──────────────────────────────────────────────────────── */
53
+
54
+ const DEFAULT_BASE_URL =
55
+ "https://ccaas-storage.worktual.co.uk/chat/ailivebot.html";
56
+
57
+ const buildUrl = (baseUrl: string, webchatId: string): string => {
58
+ const sep = baseUrl.includes("?") ? "&" : "?";
59
+ return `${baseUrl}${sep}webchatid=${webchatId}&isHeader=1`;
60
+ };
61
+
62
+ /** Injected JS – polls for chat content then posts webchat_ready */
63
+ const buildInjectedJS = (maxMs: number): string => `
64
+ (function() {
65
+ var t = setInterval(function() {
66
+ var m = document.querySelectorAll(
67
+ '.message, .chat-message, .msg-content, [class*="message"]'
68
+ );
69
+ var i = document.querySelector('input[placeholder], textarea[placeholder]');
70
+ if (m.length > 0 || i) {
71
+ clearInterval(t);
72
+ window.ReactNativeWebView.postMessage(
73
+ JSON.stringify({ type: "webchat_ready" })
74
+ );
75
+ }
76
+ }, 200);
77
+ setTimeout(function() {
78
+ clearInterval(t);
79
+ window.ReactNativeWebView.postMessage(
80
+ JSON.stringify({ type: "webchat_ready" })
81
+ );
82
+ }, ${maxMs});
83
+ })();
84
+ true;
85
+ `;
86
+
87
+ /* ────────────────────────────────────────────────────────
88
+ * Component
89
+ * ──────────────────────────────────────────────────────── */
90
+
91
+ const WorktualAIBot: React.FC<WorktualAIBotProps> = ({
92
+ webchatId,
93
+ onClose,
94
+ baseUrl = DEFAULT_BASE_URL,
95
+ loadingTitle = "AI Assistant",
96
+ loadingSubtitle = "Loading your chat...",
97
+ primaryColor = "#575CFF",
98
+ loadingBackground = "#f8f9fb",
99
+ maxLoadTime = 6000,
100
+ onReady,
101
+ onMessage: onMessageProp,
102
+ }) => {
103
+ const insets = useSafeAreaInsets();
104
+ const [showLoader, setShowLoader] = useState(true);
105
+
106
+ // Animations
107
+ const progressAnim = useRef(new Animated.Value(0)).current;
108
+ const fadeAnim = useRef(new Animated.Value(1)).current;
109
+ const dotAnim = useRef(new Animated.Value(0)).current;
110
+
111
+ const url = buildUrl(baseUrl, webchatId);
112
+ const injectedJS = buildInjectedJS(maxLoadTime);
113
+
114
+ /* ── Animations ── */
115
+ useEffect(() => {
116
+ // Progress bar: fast to 70%, then slow to 90%
117
+ Animated.sequence([
118
+ Animated.timing(progressAnim, {
119
+ toValue: 0.7,
120
+ duration: 800,
121
+ useNativeDriver: false,
122
+ }),
123
+ Animated.timing(progressAnim, {
124
+ toValue: 0.9,
125
+ duration: 2500,
126
+ useNativeDriver: false,
127
+ }),
128
+ ]).start();
129
+
130
+ // Pulsing dots
131
+ Animated.loop(
132
+ Animated.sequence([
133
+ Animated.timing(dotAnim, { toValue: 1, duration: 500, useNativeDriver: true }),
134
+ Animated.timing(dotAnim, { toValue: 0.3, duration: 500, useNativeDriver: true }),
135
+ ])
136
+ ).start();
137
+ }, []);
138
+
139
+ /* ── Hide loader ── */
140
+ const hideLoader = useCallback(() => {
141
+ if (!showLoader) return;
142
+ // Complete progress bar
143
+ Animated.timing(progressAnim, {
144
+ toValue: 1,
145
+ duration: 150,
146
+ useNativeDriver: false,
147
+ }).start();
148
+
149
+ // Fade out
150
+ setTimeout(() => {
151
+ Animated.timing(fadeAnim, {
152
+ toValue: 0,
153
+ duration: 200,
154
+ useNativeDriver: true,
155
+ }).start(() => setShowLoader(false));
156
+ }, 150);
157
+ }, [showLoader]);
158
+
159
+ /* ── WebView message handler ── */
160
+ const handleMessage = useCallback(
161
+ (event: WebViewMessageEvent) => {
162
+ try {
163
+ const data = JSON.parse(event.nativeEvent.data);
164
+
165
+ // Forward to client's onMessage if provided
166
+ onMessageProp?.(data);
167
+
168
+ if (data?.type === "webchat_ready") {
169
+ hideLoader();
170
+ onReady?.();
171
+ }
172
+ if (data?.type === "webchat_end") {
173
+ onClose();
174
+ }
175
+ } catch (_) {
176
+ // Ignore non-JSON messages
177
+ }
178
+ },
179
+ [hideLoader, onClose, onReady, onMessageProp]
180
+ );
181
+
182
+ /* ── Render ── */
183
+ const progressWidth = progressAnim.interpolate({
184
+ inputRange: [0, 1],
185
+ outputRange: ["0%", "100%"],
186
+ });
187
+
188
+ return (
189
+ <View style={[styles.container, { paddingTop: insets.top }]}>
190
+ <StatusBar barStyle="dark-content" backgroundColor="#fff" />
191
+
192
+ {/* WebView loads immediately behind the loader */}
193
+ <WebView
194
+ source={{ uri: url }}
195
+ style={styles.webview}
196
+ javaScriptEnabled
197
+ domStorageEnabled
198
+ allowsInlineMediaPlayback
199
+ mediaPlaybackRequiresUserAction={false}
200
+ onMessage={handleMessage}
201
+ injectedJavaScript={injectedJS}
202
+ // Prevent white flash on Android
203
+ {...(Platform.OS === "android" && {
204
+ overScrollMode: "never" as const,
205
+ setBuiltInZoomControls: false,
206
+ })}
207
+ />
208
+
209
+ {/* Loading overlay */}
210
+ {showLoader && (
211
+ <Animated.View
212
+ style={[
213
+ styles.loaderOverlay,
214
+ { opacity: fadeAnim, backgroundColor: loadingBackground },
215
+ ]}
216
+ >
217
+ {/* Spinner */}
218
+ <ActivityIndicator
219
+ size="large"
220
+ color={primaryColor}
221
+ style={styles.spinner}
222
+ />
223
+
224
+ {/* Title */}
225
+ <Text style={styles.loaderTitle}>{loadingTitle}</Text>
226
+
227
+ {/* Subtitle with pulsing opacity */}
228
+ <Animated.Text
229
+ style={[styles.loaderSubtitle, { opacity: dotAnim }]}
230
+ >
231
+ {loadingSubtitle}
232
+ </Animated.Text>
233
+
234
+ {/* Progress bar */}
235
+ <View style={styles.progressTrack}>
236
+ <Animated.View
237
+ style={[
238
+ styles.progressBar,
239
+ { width: progressWidth, backgroundColor: primaryColor },
240
+ ]}
241
+ />
242
+ </View>
243
+ </Animated.View>
244
+ )}
245
+ </View>
246
+ );
247
+ };
248
+
249
+ /* ────────────────────────────────────────────────────────
250
+ * Styles
251
+ * ──────────────────────────────────────────────────────── */
252
+
253
+ const styles = StyleSheet.create({
254
+ container: {
255
+ flex: 1,
256
+ backgroundColor: "#fff",
257
+ },
258
+ webview: {
259
+ flex: 1,
260
+ },
261
+ loaderOverlay: {
262
+ ...StyleSheet.absoluteFillObject,
263
+ justifyContent: "center",
264
+ alignItems: "center",
265
+ },
266
+ spinner: {
267
+ marginBottom: 20,
268
+ },
269
+ loaderTitle: {
270
+ fontSize: 18,
271
+ fontWeight: "600",
272
+ color: "#1a1a2e",
273
+ marginBottom: 6,
274
+ },
275
+ loaderSubtitle: {
276
+ fontSize: 14,
277
+ color: "#8e8ea0",
278
+ marginBottom: 28,
279
+ },
280
+ progressTrack: {
281
+ width: 200,
282
+ height: 4,
283
+ backgroundColor: "#e4e4e7",
284
+ borderRadius: 2,
285
+ overflow: "hidden",
286
+ },
287
+ progressBar: {
288
+ height: "100%",
289
+ borderRadius: 2,
290
+ },
291
+ });
292
+
293
+ export default WorktualAIBot;
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as WorktualAIBot } from "./WorktualAIBot";
2
+ export type { WorktualAIBotProps } from "./WorktualAIBot";