@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 +136 -0
- package/package.json +35 -0
- package/src/WorktualAIBot.tsx +293 -0
- package/src/index.ts +2 -0
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