react-native-ragged-chat 1.0.3

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.
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { ChatConfigResponse, RaggedConfig } from './types';
3
+ interface ChatBubbleProps {
4
+ onPress: () => void;
5
+ isOpen: boolean;
6
+ config: RaggedConfig;
7
+ serverConfig: ChatConfigResponse | null;
8
+ }
9
+ export declare const ChatBubble: React.FC<ChatBubbleProps>;
10
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ChatBubble = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const react_native_1 = require("react-native");
9
+ const react_native_svg_1 = require("react-native-svg");
10
+ const ChatBubble = ({ onPress, isOpen, config, serverConfig, }) => {
11
+ const primaryColor = serverConfig?.settings?.primaryColor || config.primaryColor || '#000000';
12
+ const shape = serverConfig?.settings?.widgetShape || config.widgetShape || 'circle';
13
+ const size = serverConfig?.settings?.widgetSize || config.widgetSize || 'medium';
14
+ const getSize = () => {
15
+ switch (size) {
16
+ case 'small':
17
+ return 48;
18
+ case 'large':
19
+ return 68;
20
+ case 'medium':
21
+ default:
22
+ return 56;
23
+ }
24
+ };
25
+ const bubbleSize = getSize();
26
+ const iconSize = size === 'small' ? 24 : size === 'large' ? 32 : 28;
27
+ const styles = react_native_1.StyleSheet.create({
28
+ bubble: {
29
+ width: bubbleSize,
30
+ height: bubbleSize,
31
+ backgroundColor: isOpen ? '#000000' : primaryColor,
32
+ justifyContent: 'center',
33
+ alignItems: 'center',
34
+ shadowColor: '#000',
35
+ shadowOffset: { width: 0, height: 4 },
36
+ shadowOpacity: 0.3,
37
+ shadowRadius: 4,
38
+ elevation: 5,
39
+ },
40
+ circle: {
41
+ borderRadius: bubbleSize / 2,
42
+ },
43
+ roundedSquare: {
44
+ borderRadius: 16,
45
+ },
46
+ });
47
+ return (<react_native_1.TouchableOpacity activeOpacity={0.8} onPress={onPress} style={[
48
+ styles.bubble,
49
+ shape === 'circle' ? styles.circle : styles.roundedSquare,
50
+ ]}>
51
+ {isOpen ? (<react_native_svg_1.Svg width={iconSize} height={iconSize} viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
52
+ <react_native_svg_1.Line x1="18" y1="6" x2="6" y2="18"/>
53
+ <react_native_svg_1.Line x1="6" y1="6" x2="18" y2="18"/>
54
+ </react_native_svg_1.Svg>) : serverConfig?.settings?.brandLogo ? (<react_native_1.Image source={{ uri: serverConfig.settings.brandLogo }} style={{ width: iconSize, height: iconSize, borderRadius: iconSize / 2 }} resizeMode="cover"/>) : (<react_native_svg_1.Svg width={iconSize} height={iconSize} viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
55
+ <react_native_svg_1.Path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
56
+ </react_native_svg_1.Svg>)}
57
+ </react_native_1.TouchableOpacity>);
58
+ };
59
+ exports.ChatBubble = ChatBubble;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Message, ChatConfigResponse, RaggedConfig } from './types';
3
+ interface ChatWindowProps {
4
+ onClose: () => void;
5
+ config: RaggedConfig;
6
+ serverConfig: ChatConfigResponse | null;
7
+ messages: Message[];
8
+ isLoading: boolean;
9
+ onSendMessage: (msg: string) => void;
10
+ }
11
+ export declare const ChatWindow: React.FC<ChatWindowProps>;
12
+ export {};
@@ -0,0 +1,258 @@
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.ChatWindow = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const react_native_svg_1 = require("react-native-svg");
40
+ const ChatWindow = ({ onClose, config, serverConfig, messages, isLoading, onSendMessage, }) => {
41
+ const [inputText, setInputText] = (0, react_1.useState)('');
42
+ const scrollViewRef = (0, react_1.useRef)(null);
43
+ const primaryColor = serverConfig?.settings?.primaryColor || config.primaryColor || '#3b82f6';
44
+ const chatTitle = serverConfig?.name || 'AI Agent';
45
+ const welcomeMessage = serverConfig?.settings?.welcomeMessage || 'How can I help you?';
46
+ const placeholderText = serverConfig?.settings?.placeholder || 'Type your message here...';
47
+ (0, react_1.useEffect)(() => {
48
+ scrollViewRef.current?.scrollToEnd({ animated: true });
49
+ }, [messages, isLoading]);
50
+ const handleSend = () => {
51
+ if (inputText.trim() && !isLoading) {
52
+ onSendMessage(inputText.trim());
53
+ setInputText('');
54
+ }
55
+ };
56
+ return (<react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.container}>
57
+ <react_native_1.View style={styles.header}>
58
+ <react_native_1.View style={styles.headerLeft}>
59
+ <react_native_1.View style={styles.avatar}>
60
+ {serverConfig?.settings?.brandLogo ? (<react_native_1.Image source={{ uri: serverConfig.settings.brandLogo }} style={{ width: '100%', height: '100%', borderRadius: 16 }} resizeMode="cover"/>) : (<react_native_svg_1.Svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2">
61
+ <react_native_svg_1.Path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
62
+ </react_native_svg_1.Svg>)}
63
+ </react_native_1.View>
64
+ <react_native_1.View>
65
+ <react_native_1.Text style={styles.title}>{chatTitle}</react_native_1.Text>
66
+ <react_native_1.Text style={styles.status}>ONLINE</react_native_1.Text>
67
+ </react_native_1.View>
68
+ </react_native_1.View>
69
+ <react_native_1.TouchableOpacity onPress={onClose} style={styles.closeBtn}>
70
+ <react_native_svg_1.Svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2">
71
+ <react_native_svg_1.Line x1="18" y1="6" x2="6" y2="18"/>
72
+ <react_native_svg_1.Line x1="6" y1="6" x2="18" y2="18"/>
73
+ </react_native_svg_1.Svg>
74
+ </react_native_1.TouchableOpacity>
75
+ </react_native_1.View>
76
+
77
+ <react_native_1.ScrollView ref={scrollViewRef} style={styles.messagesContainer} contentContainerStyle={styles.messagesContent}>
78
+ {messages.length === 0 ? (<react_native_1.View style={styles.emptyState}>
79
+ <react_native_1.View style={styles.emptyIcon}>
80
+ <react_native_svg_1.Svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#71717a" strokeWidth="2">
81
+ <react_native_svg_1.Path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
82
+ </react_native_svg_1.Svg>
83
+ </react_native_1.View>
84
+ <react_native_1.Text style={styles.emptyTitle}>{welcomeMessage}</react_native_1.Text>
85
+ </react_native_1.View>) : (messages.map((msg, index) => (<react_native_1.View key={index} style={[
86
+ styles.messageRow,
87
+ msg.role === 'user' ? styles.userRow : styles.assistantRow,
88
+ ]}>
89
+ <react_native_1.View style={[
90
+ styles.messageBubble,
91
+ msg.role === 'user' ? { backgroundColor: primaryColor } : styles.assistantBubble,
92
+ ]}>
93
+ <react_native_1.Text style={[
94
+ styles.messageText,
95
+ msg.role === 'user' ? styles.userText : styles.assistantText,
96
+ ]}>
97
+ {msg.content}
98
+ </react_native_1.Text>
99
+ </react_native_1.View>
100
+ </react_native_1.View>)))}
101
+ {isLoading && (<react_native_1.View style={[styles.messageRow, styles.assistantRow]}>
102
+ <react_native_1.View style={[styles.messageBubble, styles.assistantBubble]}>
103
+ <react_native_1.ActivityIndicator color="#fff" size="small"/>
104
+ </react_native_1.View>
105
+ </react_native_1.View>)}
106
+ </react_native_1.ScrollView>
107
+
108
+ <react_native_1.View style={styles.inputContainer}>
109
+ <react_native_1.TextInput style={styles.input} placeholder={placeholderText} placeholderTextColor="#52525b" value={inputText} onChangeText={setInputText} onSubmitEditing={handleSend} returnKeyType="send"/>
110
+ <react_native_1.TouchableOpacity style={[styles.sendBtn, { backgroundColor: primaryColor, opacity: isLoading || !inputText.trim() ? 0.5 : 1 }]} onPress={handleSend} disabled={isLoading || !inputText.trim()}>
111
+ <react_native_1.Text style={styles.sendBtnText}>Send</react_native_1.Text>
112
+ <react_native_svg_1.Svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
113
+ <react_native_svg_1.Line x1="22" y1="2" x2="11" y2="13"/>
114
+ <react_native_svg_1.Polygon points="22 2 15 22 11 13 2 9 22 2"/>
115
+ </react_native_svg_1.Svg>
116
+ </react_native_1.TouchableOpacity>
117
+ </react_native_1.View>
118
+ </react_native_1.KeyboardAvoidingView>);
119
+ };
120
+ exports.ChatWindow = ChatWindow;
121
+ const styles = react_native_1.StyleSheet.create({
122
+ container: {
123
+ flex: 1,
124
+ backgroundColor: 'rgba(9, 9, 11, 0.95)',
125
+ borderRadius: 24,
126
+ overflow: 'hidden',
127
+ borderWidth: 1,
128
+ borderColor: 'rgba(255,255,255,0.1)',
129
+ },
130
+ header: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ justifyContent: 'space-between',
134
+ padding: 16,
135
+ backgroundColor: 'white',
136
+ borderBottomWidth: 1,
137
+ borderBottomColor: 'rgba(0,0,0,0.1)',
138
+ },
139
+ headerLeft: {
140
+ flexDirection: 'row',
141
+ alignItems: 'center',
142
+ },
143
+ avatar: {
144
+ width: 32,
145
+ height: 32,
146
+ backgroundColor: '#f4f4f5',
147
+ borderRadius: 16,
148
+ alignItems: 'center',
149
+ justifyContent: 'center',
150
+ marginRight: 12,
151
+ },
152
+ title: {
153
+ fontWeight: '700',
154
+ fontSize: 14,
155
+ color: 'black',
156
+ },
157
+ status: {
158
+ fontSize: 10,
159
+ fontWeight: '700',
160
+ color: '#71717a',
161
+ marginTop: 2,
162
+ },
163
+ closeBtn: {
164
+ padding: 8,
165
+ },
166
+ messagesContainer: {
167
+ flex: 1,
168
+ },
169
+ messagesContent: {
170
+ padding: 16,
171
+ gap: 16,
172
+ },
173
+ emptyState: {
174
+ alignItems: 'center',
175
+ paddingTop: 40,
176
+ },
177
+ emptyIcon: {
178
+ width: 64,
179
+ height: 64,
180
+ backgroundColor: 'rgba(255,255,255,0.05)',
181
+ borderRadius: 16,
182
+ alignItems: 'center',
183
+ justifyContent: 'center',
184
+ marginBottom: 16,
185
+ borderWidth: 1,
186
+ borderColor: 'rgba(255,255,255,0.1)',
187
+ },
188
+ emptyTitle: {
189
+ fontSize: 18,
190
+ fontWeight: '600',
191
+ color: '#e4e4e7',
192
+ textAlign: 'center',
193
+ },
194
+ messageRow: {
195
+ flexDirection: 'row',
196
+ marginBottom: 16,
197
+ },
198
+ userRow: {
199
+ justifyContent: 'flex-end',
200
+ },
201
+ assistantRow: {
202
+ justifyContent: 'flex-start',
203
+ },
204
+ messageBubble: {
205
+ maxWidth: '85%',
206
+ padding: 14,
207
+ borderRadius: 16,
208
+ },
209
+ assistantBubble: {
210
+ backgroundColor: 'rgba(39, 39, 42, 0.8)',
211
+ borderTopLeftRadius: 4,
212
+ borderWidth: 1,
213
+ borderColor: 'rgba(255,255,255,0.1)',
214
+ },
215
+ messageText: {
216
+ fontSize: 14,
217
+ lineHeight: 20,
218
+ fontWeight: '500',
219
+ },
220
+ userText: {
221
+ color: 'white',
222
+ },
223
+ assistantText: {
224
+ color: '#fafafa',
225
+ },
226
+ inputContainer: {
227
+ flexDirection: 'row',
228
+ padding: 16,
229
+ backgroundColor: 'rgba(24, 24, 27, 0.5)',
230
+ borderTopWidth: 1,
231
+ borderTopColor: 'rgba(255,255,255,0.05)',
232
+ },
233
+ input: {
234
+ flex: 1,
235
+ backgroundColor: 'rgba(39, 39, 42, 0.5)',
236
+ borderWidth: 1,
237
+ borderColor: 'rgba(255,255,255,0.1)',
238
+ borderRadius: 16,
239
+ paddingHorizontal: 16,
240
+ paddingVertical: 12,
241
+ color: 'white',
242
+ fontSize: 14,
243
+ marginRight: 10,
244
+ },
245
+ sendBtn: {
246
+ flexDirection: 'row',
247
+ alignItems: 'center',
248
+ paddingHorizontal: 16,
249
+ paddingVertical: 12,
250
+ borderRadius: 16,
251
+ justifyContent: 'center',
252
+ },
253
+ sendBtnText: {
254
+ color: 'white',
255
+ fontWeight: '600',
256
+ marginRight: 6,
257
+ },
258
+ });
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { RaggedConfig } from './types';
3
+ interface RaggedWidgetProps {
4
+ config: RaggedConfig;
5
+ }
6
+ export declare const RaggedWidget: React.FC<RaggedWidgetProps>;
7
+ export {};
@@ -0,0 +1,124 @@
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.RaggedWidget = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const ChatBubble_1 = require("./ChatBubble");
40
+ const ChatWindow_1 = require("./ChatWindow");
41
+ const { height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');
42
+ const RaggedWidget = ({ config }) => {
43
+ const [isOpen, setIsOpen] = (0, react_1.useState)(false);
44
+ const [serverConfig, setServerConfig] = (0, react_1.useState)(null);
45
+ const [messages, setMessages] = (0, react_1.useState)([]);
46
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
47
+ const API_URL = config.apiUrl || 'https://ragflowdb.onrender.com/api';
48
+ const fetchConfig = (0, react_1.useCallback)(async () => {
49
+ try {
50
+ const response = await fetch(`${API_URL}/chat/${config.subdomain}/config`);
51
+ const data = await response.json();
52
+ setServerConfig(data);
53
+ }
54
+ catch (error) {
55
+ console.warn('[Ragged SDK] Failed to fetch config:', error);
56
+ }
57
+ }, [API_URL, config.subdomain]);
58
+ (0, react_1.useEffect)(() => {
59
+ fetchConfig();
60
+ }, [fetchConfig]);
61
+ const sendMessage = async (messageText) => {
62
+ setMessages(prev => [...prev, { role: 'user', content: messageText }]);
63
+ setIsLoading(true);
64
+ try {
65
+ const response = await fetch(`${API_URL}/chat/${config.subdomain}`, {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify({ message: messageText, history: messages }),
69
+ });
70
+ const data = await response.json();
71
+ setMessages(prev => [...prev, { role: 'assistant', content: data.response }]);
72
+ }
73
+ catch (error) {
74
+ console.warn('[Ragged SDK] Failed to send message:', error);
75
+ setMessages(prev => [
76
+ ...prev,
77
+ { role: 'assistant', content: "Sorry, I'm having trouble connecting right now." },
78
+ ]);
79
+ }
80
+ finally {
81
+ setIsLoading(false);
82
+ }
83
+ };
84
+ const toggleOpen = () => setIsOpen(!isOpen);
85
+ return (<react_native_1.View style={styles.container} pointerEvents="box-none">
86
+ {isOpen && (<react_native_1.View style={styles.chatWindowContainer}>
87
+ <ChatWindow_1.ChatWindow onClose={() => setIsOpen(false)} config={config} serverConfig={serverConfig} messages={messages} isLoading={isLoading} onSendMessage={sendMessage}/>
88
+ </react_native_1.View>)}
89
+ <react_native_1.View style={styles.bubbleContainer}>
90
+ <ChatBubble_1.ChatBubble onPress={toggleOpen} isOpen={isOpen} config={config} serverConfig={serverConfig}/>
91
+ </react_native_1.View>
92
+ </react_native_1.View>);
93
+ };
94
+ exports.RaggedWidget = RaggedWidget;
95
+ const styles = react_native_1.StyleSheet.create({
96
+ container: {
97
+ ...react_native_1.StyleSheet.absoluteFillObject,
98
+ zIndex: 999999,
99
+ flexDirection: 'column',
100
+ justifyContent: 'flex-end',
101
+ alignItems: 'flex-end',
102
+ },
103
+ chatWindowContainer: {
104
+ position: 'absolute',
105
+ bottom: 96,
106
+ right: 24,
107
+ left: react_native_1.Platform.OS === 'web' ? undefined : 24,
108
+ width: react_native_1.Platform.OS === 'web' ? 380 : 'auto',
109
+ height: Math.min(SCREEN_HEIGHT - 140, 540),
110
+ maxWidth: react_native_1.Platform.OS === 'web' ? undefined : 500,
111
+ backgroundColor: 'transparent',
112
+ shadowColor: '#000',
113
+ shadowOffset: { width: 0, height: 20 },
114
+ shadowOpacity: 0.4,
115
+ shadowRadius: 25,
116
+ elevation: 10,
117
+ borderRadius: 24,
118
+ },
119
+ bubbleContainer: {
120
+ position: 'absolute',
121
+ bottom: 24,
122
+ right: 24,
123
+ },
124
+ });
@@ -0,0 +1,2 @@
1
+ export { RaggedWidget } from './RaggedWidget';
2
+ export type { RaggedConfig, Message, ChatConfigResponse } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RaggedWidget = void 0;
4
+ var RaggedWidget_1 = require("./RaggedWidget");
5
+ Object.defineProperty(exports, "RaggedWidget", { enumerable: true, get: function () { return RaggedWidget_1.RaggedWidget; } });
@@ -0,0 +1,22 @@
1
+ export interface RaggedConfig {
2
+ subdomain: string;
3
+ apiUrl?: string;
4
+ widgetShape?: 'circle' | 'rounded-square';
5
+ widgetSize?: 'small' | 'medium' | 'large';
6
+ primaryColor?: string;
7
+ }
8
+ export interface Message {
9
+ role: 'user' | 'assistant';
10
+ content: string;
11
+ }
12
+ export interface ChatConfigResponse {
13
+ name: string;
14
+ settings: {
15
+ primaryColor: string;
16
+ placeholder: string;
17
+ welcomeMessage: string;
18
+ widgetShape: 'circle' | 'rounded-square';
19
+ widgetSize: 'small' | 'medium' | 'large';
20
+ brandLogo?: string;
21
+ };
22
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "react-native-ragged-chat",
3
+ "version": "1.0.3",
4
+ "description": "Ragged Chat SDK for React Native and Expo",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "react-native",
13
+ "expo",
14
+ "ragged",
15
+ "chat",
16
+ "widget",
17
+ "sdk"
18
+ ],
19
+ "author": "Ragged",
20
+ "license": "ISC",
21
+ "peerDependencies": {
22
+ "react": "*",
23
+ "react-native": "*",
24
+ "react-native-webview": "*"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^18.2.0",
28
+ "@types/react-native": "^0.72.0",
29
+ "typescript": "^5.0.0"
30
+ },
31
+ "dependencies": {
32
+ "react-native-safe-area-context": "^4.0.0",
33
+ "react-native-svg": "^15.15.3"
34
+ }
35
+ }
@@ -0,0 +1,87 @@
1
+ import React from 'react';
2
+ import { TouchableOpacity, StyleSheet, View, Image } from 'react-native';
3
+ import { Svg, Path, Rect, Line } from 'react-native-svg';
4
+ import { ChatConfigResponse, RaggedConfig } from './types';
5
+
6
+ interface ChatBubbleProps {
7
+ onPress: () => void;
8
+ isOpen: boolean;
9
+ config: RaggedConfig;
10
+ serverConfig: ChatConfigResponse | null;
11
+ }
12
+
13
+ export const ChatBubble: React.FC<ChatBubbleProps> = ({
14
+ onPress,
15
+ isOpen,
16
+ config,
17
+ serverConfig,
18
+ }) => {
19
+ const primaryColor =
20
+ serverConfig?.settings?.primaryColor || config.primaryColor || '#000000';
21
+ const shape = serverConfig?.settings?.widgetShape || config.widgetShape || 'circle';
22
+ const size = serverConfig?.settings?.widgetSize || config.widgetSize || 'medium';
23
+
24
+ const getSize = () => {
25
+ switch (size) {
26
+ case 'small':
27
+ return 48;
28
+ case 'large':
29
+ return 68;
30
+ case 'medium':
31
+ default:
32
+ return 56;
33
+ }
34
+ };
35
+
36
+ const bubbleSize = getSize();
37
+ const iconSize = size === 'small' ? 24 : size === 'large' ? 32 : 28;
38
+
39
+ const styles = StyleSheet.create({
40
+ bubble: {
41
+ width: bubbleSize,
42
+ height: bubbleSize,
43
+ backgroundColor: isOpen ? '#000000' : primaryColor,
44
+ justifyContent: 'center',
45
+ alignItems: 'center',
46
+ shadowColor: '#000',
47
+ shadowOffset: { width: 0, height: 4 },
48
+ shadowOpacity: 0.3,
49
+ shadowRadius: 4,
50
+ elevation: 5,
51
+ },
52
+ circle: {
53
+ borderRadius: bubbleSize / 2,
54
+ },
55
+ roundedSquare: {
56
+ borderRadius: 16,
57
+ },
58
+ });
59
+
60
+ return (
61
+ <TouchableOpacity
62
+ activeOpacity={0.8}
63
+ onPress={onPress}
64
+ style={[
65
+ styles.bubble,
66
+ shape === 'circle' ? styles.circle : styles.roundedSquare,
67
+ ]}
68
+ >
69
+ {isOpen ? (
70
+ <Svg width={iconSize} height={iconSize} viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
71
+ <Line x1="18" y1="6" x2="6" y2="18" />
72
+ <Line x1="6" y1="6" x2="18" y2="18" />
73
+ </Svg>
74
+ ) : serverConfig?.settings?.brandLogo ? (
75
+ <Image
76
+ source={{ uri: serverConfig.settings.brandLogo }}
77
+ style={{ width: iconSize, height: iconSize, borderRadius: iconSize / 2 }}
78
+ resizeMode="cover"
79
+ />
80
+ ) : (
81
+ <Svg width={iconSize} height={iconSize} viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
82
+ <Path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
83
+ </Svg>
84
+ )}
85
+ </TouchableOpacity>
86
+ );
87
+ };
@@ -0,0 +1,300 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ StyleSheet,
9
+ ActivityIndicator,
10
+ KeyboardAvoidingView,
11
+ Platform,
12
+ Image,
13
+ } from 'react-native';
14
+ import { Svg, Path, Line, Polygon } from 'react-native-svg';
15
+ import { Message, ChatConfigResponse, RaggedConfig } from './types';
16
+
17
+ interface ChatWindowProps {
18
+ onClose: () => void;
19
+ config: RaggedConfig;
20
+ serverConfig: ChatConfigResponse | null;
21
+ messages: Message[];
22
+ isLoading: boolean;
23
+ onSendMessage: (msg: string) => void;
24
+ }
25
+
26
+ export const ChatWindow: React.FC<ChatWindowProps> = ({
27
+ onClose,
28
+ config,
29
+ serverConfig,
30
+ messages,
31
+ isLoading,
32
+ onSendMessage,
33
+ }) => {
34
+ const [inputText, setInputText] = useState('');
35
+ const scrollViewRef = useRef<ScrollView>(null);
36
+
37
+ const primaryColor =
38
+ serverConfig?.settings?.primaryColor || config.primaryColor || '#3b82f6';
39
+ const chatTitle = serverConfig?.name || 'AI Agent';
40
+ const welcomeMessage = serverConfig?.settings?.welcomeMessage || 'How can I help you?';
41
+ const placeholderText = serverConfig?.settings?.placeholder || 'Type your message here...';
42
+
43
+ useEffect(() => {
44
+ scrollViewRef.current?.scrollToEnd({ animated: true });
45
+ }, [messages, isLoading]);
46
+
47
+ const handleSend = () => {
48
+ if (inputText.trim() && !isLoading) {
49
+ onSendMessage(inputText.trim());
50
+ setInputText('');
51
+ }
52
+ };
53
+
54
+ return (
55
+ <KeyboardAvoidingView
56
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
57
+ style={styles.container}
58
+ >
59
+ <View style={styles.header}>
60
+ <View style={styles.headerLeft}>
61
+ <View style={styles.avatar}>
62
+ {serverConfig?.settings?.brandLogo ? (
63
+ <Image
64
+ source={{ uri: serverConfig.settings.brandLogo }}
65
+ style={{ width: '100%', height: '100%', borderRadius: 16 }}
66
+ resizeMode="cover"
67
+ />
68
+ ) : (
69
+ <Svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2">
70
+ <Path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
71
+ </Svg>
72
+ )}
73
+ </View>
74
+ <View>
75
+ <Text style={styles.title}>{chatTitle}</Text>
76
+ <Text style={styles.status}>ONLINE</Text>
77
+ </View>
78
+ </View>
79
+ <TouchableOpacity onPress={onClose} style={styles.closeBtn}>
80
+ <Svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="black" strokeWidth="2">
81
+ <Line x1="18" y1="6" x2="6" y2="18" />
82
+ <Line x1="6" y1="6" x2="18" y2="18" />
83
+ </Svg>
84
+ </TouchableOpacity>
85
+ </View>
86
+
87
+ <ScrollView
88
+ ref={scrollViewRef}
89
+ style={styles.messagesContainer}
90
+ contentContainerStyle={styles.messagesContent}
91
+ >
92
+ {messages.length === 0 ? (
93
+ <View style={styles.emptyState}>
94
+ <View style={styles.emptyIcon}>
95
+ <Svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#71717a" strokeWidth="2">
96
+ <Path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
97
+ </Svg>
98
+ </View>
99
+ <Text style={styles.emptyTitle}>{welcomeMessage}</Text>
100
+ </View>
101
+ ) : (
102
+ messages.map((msg, index) => (
103
+ <View
104
+ key={index}
105
+ style={[
106
+ styles.messageRow,
107
+ msg.role === 'user' ? styles.userRow : styles.assistantRow,
108
+ ]}
109
+ >
110
+ <View
111
+ style={[
112
+ styles.messageBubble,
113
+ msg.role === 'user' ? { backgroundColor: primaryColor } : styles.assistantBubble,
114
+ ]}
115
+ >
116
+ <Text
117
+ style={[
118
+ styles.messageText,
119
+ msg.role === 'user' ? styles.userText : styles.assistantText,
120
+ ]}
121
+ >
122
+ {msg.content}
123
+ </Text>
124
+ </View>
125
+ </View>
126
+ ))
127
+ )}
128
+ {isLoading && (
129
+ <View style={[styles.messageRow, styles.assistantRow]}>
130
+ <View style={[styles.messageBubble, styles.assistantBubble]}>
131
+ <ActivityIndicator color="#fff" size="small" />
132
+ </View>
133
+ </View>
134
+ )}
135
+ </ScrollView>
136
+
137
+ <View style={styles.inputContainer}>
138
+ <TextInput
139
+ style={styles.input}
140
+ placeholder={placeholderText}
141
+ placeholderTextColor="#52525b"
142
+ value={inputText}
143
+ onChangeText={setInputText}
144
+ onSubmitEditing={handleSend}
145
+ returnKeyType="send"
146
+ />
147
+ <TouchableOpacity
148
+ style={[styles.sendBtn, { backgroundColor: primaryColor, opacity: isLoading || !inputText.trim() ? 0.5 : 1 }]}
149
+ onPress={handleSend}
150
+ disabled={isLoading || !inputText.trim()}
151
+ >
152
+ <Text style={styles.sendBtnText}>Send</Text>
153
+ <Svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2">
154
+ <Line x1="22" y1="2" x2="11" y2="13" />
155
+ <Polygon points="22 2 15 22 11 13 2 9 22 2" />
156
+ </Svg>
157
+ </TouchableOpacity>
158
+ </View>
159
+ </KeyboardAvoidingView>
160
+ );
161
+ };
162
+
163
+ const styles = StyleSheet.create({
164
+ container: {
165
+ flex: 1,
166
+ backgroundColor: 'rgba(9, 9, 11, 0.95)',
167
+ borderRadius: 24,
168
+ overflow: 'hidden',
169
+ borderWidth: 1,
170
+ borderColor: 'rgba(255,255,255,0.1)',
171
+ },
172
+ header: {
173
+ flexDirection: 'row',
174
+ alignItems: 'center',
175
+ justifyContent: 'space-between',
176
+ padding: 16,
177
+ backgroundColor: 'white',
178
+ borderBottomWidth: 1,
179
+ borderBottomColor: 'rgba(0,0,0,0.1)',
180
+ },
181
+ headerLeft: {
182
+ flexDirection: 'row',
183
+ alignItems: 'center',
184
+ },
185
+ avatar: {
186
+ width: 32,
187
+ height: 32,
188
+ backgroundColor: '#f4f4f5',
189
+ borderRadius: 16,
190
+ alignItems: 'center',
191
+ justifyContent: 'center',
192
+ marginRight: 12,
193
+ },
194
+ title: {
195
+ fontWeight: '700',
196
+ fontSize: 14,
197
+ color: 'black',
198
+ },
199
+ status: {
200
+ fontSize: 10,
201
+ fontWeight: '700',
202
+ color: '#71717a',
203
+ marginTop: 2,
204
+ },
205
+ closeBtn: {
206
+ padding: 8,
207
+ },
208
+ messagesContainer: {
209
+ flex: 1,
210
+ },
211
+ messagesContent: {
212
+ padding: 16,
213
+ gap: 16,
214
+ },
215
+ emptyState: {
216
+ alignItems: 'center',
217
+ paddingTop: 40,
218
+ },
219
+ emptyIcon: {
220
+ width: 64,
221
+ height: 64,
222
+ backgroundColor: 'rgba(255,255,255,0.05)',
223
+ borderRadius: 16,
224
+ alignItems: 'center',
225
+ justifyContent: 'center',
226
+ marginBottom: 16,
227
+ borderWidth: 1,
228
+ borderColor: 'rgba(255,255,255,0.1)',
229
+ },
230
+ emptyTitle: {
231
+ fontSize: 18,
232
+ fontWeight: '600',
233
+ color: '#e4e4e7',
234
+ textAlign: 'center',
235
+ },
236
+ messageRow: {
237
+ flexDirection: 'row',
238
+ marginBottom: 16,
239
+ },
240
+ userRow: {
241
+ justifyContent: 'flex-end',
242
+ },
243
+ assistantRow: {
244
+ justifyContent: 'flex-start',
245
+ },
246
+ messageBubble: {
247
+ maxWidth: '85%',
248
+ padding: 14,
249
+ borderRadius: 16,
250
+ },
251
+ assistantBubble: {
252
+ backgroundColor: 'rgba(39, 39, 42, 0.8)',
253
+ borderTopLeftRadius: 4,
254
+ borderWidth: 1,
255
+ borderColor: 'rgba(255,255,255,0.1)',
256
+ },
257
+ messageText: {
258
+ fontSize: 14,
259
+ lineHeight: 20,
260
+ fontWeight: '500',
261
+ },
262
+ userText: {
263
+ color: 'white',
264
+ },
265
+ assistantText: {
266
+ color: '#fafafa',
267
+ },
268
+ inputContainer: {
269
+ flexDirection: 'row',
270
+ padding: 16,
271
+ backgroundColor: 'rgba(24, 24, 27, 0.5)',
272
+ borderTopWidth: 1,
273
+ borderTopColor: 'rgba(255,255,255,0.05)',
274
+ },
275
+ input: {
276
+ flex: 1,
277
+ backgroundColor: 'rgba(39, 39, 42, 0.5)',
278
+ borderWidth: 1,
279
+ borderColor: 'rgba(255,255,255,0.1)',
280
+ borderRadius: 16,
281
+ paddingHorizontal: 16,
282
+ paddingVertical: 12,
283
+ color: 'white',
284
+ fontSize: 14,
285
+ marginRight: 10,
286
+ },
287
+ sendBtn: {
288
+ flexDirection: 'row',
289
+ alignItems: 'center',
290
+ paddingHorizontal: 16,
291
+ paddingVertical: 12,
292
+ borderRadius: 16,
293
+ justifyContent: 'center',
294
+ },
295
+ sendBtnText: {
296
+ color: 'white',
297
+ fontWeight: '600',
298
+ marginRight: 6,
299
+ },
300
+ });
@@ -0,0 +1,115 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { View, StyleSheet, Dimensions, Platform } from 'react-native';
3
+ import { ChatBubble } from './ChatBubble';
4
+ import { ChatWindow } from './ChatWindow';
5
+ import { RaggedConfig, ChatConfigResponse, Message } from './types';
6
+
7
+ const { height: SCREEN_HEIGHT } = Dimensions.get('window');
8
+
9
+ interface RaggedWidgetProps {
10
+ config: RaggedConfig;
11
+ }
12
+
13
+ export const RaggedWidget: React.FC<RaggedWidgetProps> = ({ config }) => {
14
+ const [isOpen, setIsOpen] = useState(false);
15
+ const [serverConfig, setServerConfig] = useState<ChatConfigResponse | null>(null);
16
+ const [messages, setMessages] = useState<Message[]>([]);
17
+ const [isLoading, setIsLoading] = useState(false);
18
+
19
+ const API_URL = config.apiUrl || 'https://ragflowdb.onrender.com/api';
20
+
21
+ const fetchConfig = useCallback(async () => {
22
+ try {
23
+ const response = await fetch(`${API_URL}/chat/${config.subdomain}/config`);
24
+ const data = await response.json();
25
+ setServerConfig(data);
26
+ } catch (error) {
27
+ console.warn('[Ragged SDK] Failed to fetch config:', error);
28
+ }
29
+ }, [API_URL, config.subdomain]);
30
+
31
+ useEffect(() => {
32
+ fetchConfig();
33
+ }, [fetchConfig]);
34
+
35
+ const sendMessage = async (messageText: string) => {
36
+ setMessages(prev => [...prev, { role: 'user', content: messageText }]);
37
+ setIsLoading(true);
38
+
39
+ try {
40
+ const response = await fetch(`${API_URL}/chat/${config.subdomain}`, {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify({ message: messageText, history: messages }),
44
+ });
45
+ const data = await response.json();
46
+ setMessages(prev => [...prev, { role: 'assistant', content: data.response }]);
47
+ } catch (error) {
48
+ console.warn('[Ragged SDK] Failed to send message:', error);
49
+ setMessages(prev => [
50
+ ...prev,
51
+ { role: 'assistant', content: "Sorry, I'm having trouble connecting right now." },
52
+ ]);
53
+ } finally {
54
+ setIsLoading(false);
55
+ }
56
+ };
57
+
58
+ const toggleOpen = () => setIsOpen(!isOpen);
59
+
60
+ return (
61
+ <View style={styles.container} pointerEvents="box-none">
62
+ {isOpen && (
63
+ <View style={styles.chatWindowContainer}>
64
+ <ChatWindow
65
+ onClose={() => setIsOpen(false)}
66
+ config={config}
67
+ serverConfig={serverConfig}
68
+ messages={messages}
69
+ isLoading={isLoading}
70
+ onSendMessage={sendMessage}
71
+ />
72
+ </View>
73
+ )}
74
+ <View style={styles.bubbleContainer}>
75
+ <ChatBubble
76
+ onPress={toggleOpen}
77
+ isOpen={isOpen}
78
+ config={config}
79
+ serverConfig={serverConfig}
80
+ />
81
+ </View>
82
+ </View>
83
+ );
84
+ };
85
+
86
+ const styles = StyleSheet.create({
87
+ container: {
88
+ ...StyleSheet.absoluteFillObject,
89
+ zIndex: 999999,
90
+ flexDirection: 'column',
91
+ justifyContent: 'flex-end',
92
+ alignItems: 'flex-end',
93
+ },
94
+ chatWindowContainer: {
95
+ position: 'absolute',
96
+ bottom: 96,
97
+ right: 24,
98
+ left: Platform.OS === 'web' ? undefined : 24,
99
+ width: Platform.OS === 'web' ? 380 : 'auto',
100
+ height: Math.min(SCREEN_HEIGHT - 140, 540),
101
+ maxWidth: Platform.OS === 'web' ? undefined : 500,
102
+ backgroundColor: 'transparent',
103
+ shadowColor: '#000',
104
+ shadowOffset: { width: 0, height: 20 },
105
+ shadowOpacity: 0.4,
106
+ shadowRadius: 25,
107
+ elevation: 10,
108
+ borderRadius: 24,
109
+ },
110
+ bubbleContainer: {
111
+ position: 'absolute',
112
+ bottom: 24,
113
+ right: 24,
114
+ },
115
+ });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { RaggedWidget } from './RaggedWidget';
2
+ export type { RaggedConfig, Message, ChatConfigResponse } from './types';
package/src/types.ts ADDED
@@ -0,0 +1,24 @@
1
+ export interface RaggedConfig {
2
+ subdomain: string;
3
+ apiUrl?: string;
4
+ widgetShape?: 'circle' | 'rounded-square';
5
+ widgetSize?: 'small' | 'medium' | 'large';
6
+ primaryColor?: string;
7
+ }
8
+
9
+ export interface Message {
10
+ role: 'user' | 'assistant';
11
+ content: string;
12
+ }
13
+
14
+ export interface ChatConfigResponse {
15
+ name: string;
16
+ settings: {
17
+ primaryColor: string;
18
+ placeholder: string;
19
+ welcomeMessage: string;
20
+ widgetShape: 'circle' | 'rounded-square';
21
+ widgetSize: 'small' | 'medium' | 'large';
22
+ brandLogo?: string;
23
+ };
24
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "commonjs",
5
+ "lib": [
6
+ "es2017"
7
+ ],
8
+ "declaration": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "jsx": "react-native"
16
+ },
17
+ "include": [
18
+ "src/**/*"
19
+ ]
20
+ }