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.
- package/dist/ChatBubble.d.ts +10 -0
- package/dist/ChatBubble.js +59 -0
- package/dist/ChatWindow.d.ts +12 -0
- package/dist/ChatWindow.js +258 -0
- package/dist/RaggedWidget.d.ts +7 -0
- package/dist/RaggedWidget.js +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.js +2 -0
- package/package.json +35 -0
- package/src/ChatBubble.tsx +87 -0
- package/src/ChatWindow.tsx +300 -0
- package/src/RaggedWidget.tsx +115 -0
- package/src/index.ts +2 -0
- package/src/types.ts +24 -0
- package/tsconfig.json +20 -0
|
@@ -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,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
|
+
});
|
package/dist/index.d.ts
ADDED
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; } });
|
package/dist/types.d.ts
ADDED
|
@@ -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
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
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
|
+
}
|