@happiest-team/ai-chat-native 1.0.1
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 +66 -0
- package/dist/components/ChatPanel.d.ts +4 -0
- package/dist/components/ChatPanel.js +241 -0
- package/dist/components/HappiestProvider.d.ts +8 -0
- package/dist/components/HappiestProvider.js +12 -0
- package/dist/hooks/useHappiestChat.d.ts +6 -0
- package/dist/hooks/useHappiestChat.js +20 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/src/components/ChatPanel.d.ts +4 -0
- package/dist/src/components/ChatPanel.js +270 -0
- package/dist/src/components/HappiestProvider.d.ts +8 -0
- package/dist/src/components/HappiestProvider.js +49 -0
- package/dist/src/hooks/useHappiestChat.d.ts +5 -0
- package/dist/src/hooks/useHappiestChat.js +22 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +10 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @happiest-team/ai-chat-native
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@happiest-team/ai-chat-native)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://reactnative.dev/)
|
|
6
|
+
|
|
7
|
+
The official React Native SDK for **Happiest Chat**. Embed native, high-performance AI chat agents into your iOS and Android mobile applications seamlessly.
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
- **Native Performance:** Fully optimized using `Animated` API and React Native UI threads for butter-smooth interactions.
|
|
12
|
+
- **Premium Native UI:** `<ChatPanel />` comes with iOS-style bottom sheets, bouncing dot animations, and keyboard-avoiding views.
|
|
13
|
+
- **Headless Hooks:** `useHappiestChat()` available for building entirely custom native interfaces.
|
|
14
|
+
- **Cross-Platform:** Write once, works perfectly on both iOS and Android.
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @happiest-team/ai-chat-native @happiest-team/ai-chat-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 🛠 Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Setup the Provider
|
|
25
|
+
Wrap your app root with `HappiestProvider`.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { HappiestProvider } from '@happiest-team/ai-chat-native';
|
|
29
|
+
|
|
30
|
+
export default function App() {
|
|
31
|
+
return (
|
|
32
|
+
<HappiestProvider
|
|
33
|
+
clubId="YOUR_CLUB_ID"
|
|
34
|
+
config={{
|
|
35
|
+
publishableKey: "pk_live_your_key"
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
<YourAppNavigator />
|
|
39
|
+
</HappiestProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Render the Chat Screen
|
|
45
|
+
Drop the native `ChatPanel` into your Screen or Modal.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { ChatPanel } from '@happiest-team/ai-chat-native';
|
|
49
|
+
import { View, StyleSheet } from 'react-native';
|
|
50
|
+
|
|
51
|
+
export function ChatScreen() {
|
|
52
|
+
return (
|
|
53
|
+
<View style={styles.container}>
|
|
54
|
+
{/* The ChatPanel automatically handles KeyboardAvoidingView */}
|
|
55
|
+
<ChatPanel />
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const styles = StyleSheet.create({
|
|
61
|
+
container: { flex: 1 }
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 📄 License
|
|
66
|
+
MIT © Happiest
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet, KeyboardAvoidingView, Platform, Image, Animated, SafeAreaView } from 'react-native';
|
|
3
|
+
import { useHappiestChat } from '../hooks/useHappiestChat';
|
|
4
|
+
const TypingIndicator = () => {
|
|
5
|
+
const anim1 = useRef(new Animated.Value(0)).current;
|
|
6
|
+
const anim2 = useRef(new Animated.Value(0)).current;
|
|
7
|
+
const anim3 = useRef(new Animated.Value(0)).current;
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const animateDot = (anim, delay) => {
|
|
10
|
+
Animated.loop(Animated.sequence([
|
|
11
|
+
Animated.delay(delay),
|
|
12
|
+
Animated.timing(anim, { toValue: 1, duration: 350, useNativeDriver: true }),
|
|
13
|
+
Animated.timing(anim, { toValue: 0, duration: 350, useNativeDriver: true }),
|
|
14
|
+
Animated.delay(700),
|
|
15
|
+
])).start();
|
|
16
|
+
};
|
|
17
|
+
animateDot(anim1, 0);
|
|
18
|
+
animateDot(anim2, 160);
|
|
19
|
+
animateDot(anim3, 320);
|
|
20
|
+
}, []);
|
|
21
|
+
return (<View style={styles.typingBubble}>
|
|
22
|
+
<Animated.View style={[styles.typingDot, { transform: [{ scale: anim1 }] }]}/>
|
|
23
|
+
<Animated.View style={[styles.typingDot, { transform: [{ scale: anim2 }] }]}/>
|
|
24
|
+
<Animated.View style={[styles.typingDot, { transform: [{ scale: anim3 }] }]}/>
|
|
25
|
+
</View>);
|
|
26
|
+
};
|
|
27
|
+
export function ChatPanel({ style }) {
|
|
28
|
+
const { messages, isTyping, sendMessage, retryMessage, widgetConfig } = useHappiestChat();
|
|
29
|
+
const [input, setInput] = useState('');
|
|
30
|
+
const flatListRef = useRef(null);
|
|
31
|
+
const safeConfig = widgetConfig || {};
|
|
32
|
+
const primaryColor = safeConfig.theme?.primary || safeConfig.primaryColor || '#0A65CC';
|
|
33
|
+
const iconUrl = safeConfig.iconUrl;
|
|
34
|
+
const agentName = safeConfig.agentName || 'Chat Assistant';
|
|
35
|
+
const clubName = safeConfig.clubName || 'Club';
|
|
36
|
+
const welcomeMessage = safeConfig.welcomeMessage || 'Hi there! How can I help you today?';
|
|
37
|
+
const handleSend = () => {
|
|
38
|
+
if (!input.trim())
|
|
39
|
+
return;
|
|
40
|
+
sendMessage(input);
|
|
41
|
+
setInput('');
|
|
42
|
+
};
|
|
43
|
+
const renderItem = ({ item }) => {
|
|
44
|
+
const isUser = item.isUser;
|
|
45
|
+
const isFailed = item.status === 'failed';
|
|
46
|
+
return (<View style={[styles.messageWrapper, isUser ? styles.messageWrapperUser : styles.messageWrapperAgent]}>
|
|
47
|
+
<View style={isUser ? { alignItems: 'flex-end' } : { alignItems: 'flex-start' }}>
|
|
48
|
+
<View style={[
|
|
49
|
+
styles.messageBubble,
|
|
50
|
+
isUser ? { backgroundColor: isFailed ? '#ef4444' : primaryColor, borderBottomRightRadius: 4 } : { ...styles.messageBubbleAgent, borderBottomLeftRadius: 4 },
|
|
51
|
+
isFailed && { opacity: 0.9 }
|
|
52
|
+
]}>
|
|
53
|
+
<Text style={isUser ? styles.messageTextUser : styles.messageTextAgent}>
|
|
54
|
+
{item.text}
|
|
55
|
+
</Text>
|
|
56
|
+
</View>
|
|
57
|
+
{isFailed && (<TouchableOpacity onPress={() => retryMessage(item.id)} style={{ marginTop: 6, flexDirection: 'row', alignItems: 'center' }}>
|
|
58
|
+
<Text style={{ color: '#ef4444', fontSize: 11, fontWeight: '500' }}>⚠️ Failed to send. Tap to retry</Text>
|
|
59
|
+
</TouchableOpacity>)}
|
|
60
|
+
</View>
|
|
61
|
+
</View>);
|
|
62
|
+
};
|
|
63
|
+
const renderHeader = () => (<View style={styles.welcomeWrapper}>
|
|
64
|
+
<View style={[styles.messageBubble, styles.messageBubbleAgent, { borderBottomLeftRadius: 4 }]}>
|
|
65
|
+
<Text style={styles.messageTextAgent}>{welcomeMessage}</Text>
|
|
66
|
+
</View>
|
|
67
|
+
</View>);
|
|
68
|
+
return (<SafeAreaView style={[styles.safeArea, { backgroundColor: primaryColor }, style]}>
|
|
69
|
+
<KeyboardAvoidingView style={styles.container} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
70
|
+
{/* Header */}
|
|
71
|
+
<View style={[styles.header, { backgroundColor: primaryColor }]}>
|
|
72
|
+
<View style={styles.headerInfo}>
|
|
73
|
+
{iconUrl ? (<Image source={{ uri: iconUrl }} style={styles.headerIcon} resizeMode="cover"/>) : (<View style={styles.headerIconPlaceholder}>
|
|
74
|
+
<Text style={styles.headerIconText}>{agentName.charAt(0)}</Text>
|
|
75
|
+
</View>)}
|
|
76
|
+
<View>
|
|
77
|
+
<Text style={styles.headerTitle}>{agentName}</Text>
|
|
78
|
+
<Text style={styles.headerSubtitle}>Powered by Happiest for {clubName}</Text>
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
</View>
|
|
82
|
+
|
|
83
|
+
{/* Message List */}
|
|
84
|
+
<FlatList ref={flatListRef} data={messages} keyExtractor={(item) => item.id} renderItem={renderItem} ListHeaderComponent={renderHeader} contentContainerStyle={styles.listContent} onContentSizeChange={() => flatListRef.current?.scrollToEnd({ animated: true })} onLayout={() => flatListRef.current?.scrollToEnd({ animated: true })} ListFooterComponent={() => isTyping ? <TypingIndicator /> : <View style={{ height: 16 }}/>}/>
|
|
85
|
+
|
|
86
|
+
{/* Input Area */}
|
|
87
|
+
<View style={styles.inputContainer}>
|
|
88
|
+
<TextInput style={styles.input} value={input} onChangeText={setInput} placeholder="Type your message..." placeholderTextColor="#999" returnKeyType="send" onSubmitEditing={handleSend} underlineColorAndroid="transparent"/>
|
|
89
|
+
<TouchableOpacity style={[styles.sendButton, { backgroundColor: input.trim() ? primaryColor : '#D1D5DB' }]} onPress={handleSend} disabled={!input.trim()}>
|
|
90
|
+
<Text style={styles.sendButtonArrow}>➤</Text>
|
|
91
|
+
</TouchableOpacity>
|
|
92
|
+
</View>
|
|
93
|
+
</KeyboardAvoidingView>
|
|
94
|
+
</SafeAreaView>);
|
|
95
|
+
}
|
|
96
|
+
const styles = StyleSheet.create({
|
|
97
|
+
safeArea: {
|
|
98
|
+
flex: 1,
|
|
99
|
+
},
|
|
100
|
+
container: {
|
|
101
|
+
flex: 1,
|
|
102
|
+
backgroundColor: '#F9FAFB',
|
|
103
|
+
},
|
|
104
|
+
header: {
|
|
105
|
+
flexDirection: 'row',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
padding: 20,
|
|
108
|
+
paddingTop: Platform.OS === 'ios' ? 20 : 40,
|
|
109
|
+
justifyContent: 'space-between',
|
|
110
|
+
},
|
|
111
|
+
headerInfo: {
|
|
112
|
+
flexDirection: 'row',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
},
|
|
115
|
+
headerIcon: {
|
|
116
|
+
width: 32,
|
|
117
|
+
height: 32,
|
|
118
|
+
borderRadius: 16,
|
|
119
|
+
marginRight: 12,
|
|
120
|
+
backgroundColor: '#FFF',
|
|
121
|
+
},
|
|
122
|
+
headerIconPlaceholder: {
|
|
123
|
+
width: 32,
|
|
124
|
+
height: 32,
|
|
125
|
+
borderRadius: 16,
|
|
126
|
+
marginRight: 12,
|
|
127
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
128
|
+
alignItems: 'center',
|
|
129
|
+
justifyContent: 'center',
|
|
130
|
+
},
|
|
131
|
+
headerIconText: {
|
|
132
|
+
color: '#FFF',
|
|
133
|
+
fontSize: 16,
|
|
134
|
+
fontWeight: 'bold',
|
|
135
|
+
},
|
|
136
|
+
headerTitle: {
|
|
137
|
+
color: '#FFF',
|
|
138
|
+
fontSize: 16,
|
|
139
|
+
fontWeight: '600',
|
|
140
|
+
},
|
|
141
|
+
headerSubtitle: {
|
|
142
|
+
color: 'rgba(255,255,255,0.9)',
|
|
143
|
+
fontSize: 13,
|
|
144
|
+
marginTop: 2,
|
|
145
|
+
},
|
|
146
|
+
listContent: {
|
|
147
|
+
padding: 20,
|
|
148
|
+
paddingBottom: 8,
|
|
149
|
+
},
|
|
150
|
+
welcomeWrapper: {
|
|
151
|
+
marginBottom: 12,
|
|
152
|
+
flexDirection: 'row',
|
|
153
|
+
justifyContent: 'flex-start',
|
|
154
|
+
},
|
|
155
|
+
messageWrapper: {
|
|
156
|
+
marginBottom: 12,
|
|
157
|
+
flexDirection: 'row',
|
|
158
|
+
},
|
|
159
|
+
messageWrapperUser: {
|
|
160
|
+
justifyContent: 'flex-end',
|
|
161
|
+
},
|
|
162
|
+
messageWrapperAgent: {
|
|
163
|
+
justifyContent: 'flex-start',
|
|
164
|
+
},
|
|
165
|
+
messageBubble: {
|
|
166
|
+
maxWidth: '85%',
|
|
167
|
+
paddingHorizontal: 16,
|
|
168
|
+
paddingVertical: 12,
|
|
169
|
+
borderRadius: 12,
|
|
170
|
+
},
|
|
171
|
+
messageBubbleAgent: {
|
|
172
|
+
backgroundColor: '#FFFFFF',
|
|
173
|
+
borderWidth: 1,
|
|
174
|
+
borderColor: '#E5E7EB',
|
|
175
|
+
},
|
|
176
|
+
messageTextUser: {
|
|
177
|
+
color: '#FFFFFF',
|
|
178
|
+
fontSize: 14,
|
|
179
|
+
lineHeight: 20,
|
|
180
|
+
},
|
|
181
|
+
messageTextAgent: {
|
|
182
|
+
color: '#1F2937',
|
|
183
|
+
fontSize: 14,
|
|
184
|
+
lineHeight: 20,
|
|
185
|
+
},
|
|
186
|
+
typingBubble: {
|
|
187
|
+
alignSelf: 'flex-start',
|
|
188
|
+
backgroundColor: '#FFFFFF',
|
|
189
|
+
borderWidth: 1,
|
|
190
|
+
borderColor: '#E5E7EB',
|
|
191
|
+
paddingHorizontal: 16,
|
|
192
|
+
paddingVertical: 14,
|
|
193
|
+
borderRadius: 12,
|
|
194
|
+
borderBottomLeftRadius: 4,
|
|
195
|
+
flexDirection: 'row',
|
|
196
|
+
alignItems: 'center',
|
|
197
|
+
marginBottom: 16,
|
|
198
|
+
},
|
|
199
|
+
typingDot: {
|
|
200
|
+
width: 6,
|
|
201
|
+
height: 6,
|
|
202
|
+
backgroundColor: '#666666',
|
|
203
|
+
borderRadius: 3,
|
|
204
|
+
marginHorizontal: 2,
|
|
205
|
+
},
|
|
206
|
+
inputContainer: {
|
|
207
|
+
flexDirection: 'row',
|
|
208
|
+
padding: 16,
|
|
209
|
+
backgroundColor: '#FFFFFF',
|
|
210
|
+
borderTopWidth: 1,
|
|
211
|
+
borderTopColor: '#E5E7EB',
|
|
212
|
+
alignItems: 'center',
|
|
213
|
+
},
|
|
214
|
+
input: {
|
|
215
|
+
flex: 1,
|
|
216
|
+
minHeight: 40,
|
|
217
|
+
maxHeight: 100,
|
|
218
|
+
backgroundColor: '#FFFFFF',
|
|
219
|
+
borderWidth: 1,
|
|
220
|
+
borderColor: '#E5E7EB',
|
|
221
|
+
borderRadius: 24,
|
|
222
|
+
paddingHorizontal: 16,
|
|
223
|
+
paddingVertical: 10,
|
|
224
|
+
marginRight: 12,
|
|
225
|
+
fontSize: 14,
|
|
226
|
+
color: '#1F2937',
|
|
227
|
+
},
|
|
228
|
+
sendButton: {
|
|
229
|
+
width: 40,
|
|
230
|
+
height: 40,
|
|
231
|
+
borderRadius: 20,
|
|
232
|
+
justifyContent: 'center',
|
|
233
|
+
alignItems: 'center',
|
|
234
|
+
},
|
|
235
|
+
sendButtonArrow: {
|
|
236
|
+
color: '#FFFFFF',
|
|
237
|
+
fontSize: 16,
|
|
238
|
+
marginLeft: 2,
|
|
239
|
+
marginTop: -2,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { HappiestClient, HappiestConfig } from '@happiest-team/ai-chat-core';
|
|
3
|
+
export declare const HappiestContext: React.Context<HappiestClient | null>;
|
|
4
|
+
export interface HappiestProviderProps {
|
|
5
|
+
config: HappiestConfig;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function HappiestProvider({ config, children }: HappiestProviderProps): React.JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { createContext, useRef } from 'react';
|
|
2
|
+
import { HappiestClient } from '@happiest-team/ai-chat-core';
|
|
3
|
+
export const HappiestContext = createContext(null);
|
|
4
|
+
export function HappiestProvider({ config, children }) {
|
|
5
|
+
const clientRef = useRef(null);
|
|
6
|
+
if (!clientRef.current) {
|
|
7
|
+
clientRef.current = new HappiestClient(config);
|
|
8
|
+
}
|
|
9
|
+
return (<HappiestContext.Provider value={clientRef.current}>
|
|
10
|
+
{children}
|
|
11
|
+
</HappiestContext.Provider>);
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import { HappiestContext } from '../components/HappiestProvider';
|
|
3
|
+
export function useHappiestChat() {
|
|
4
|
+
const client = useContext(HappiestContext);
|
|
5
|
+
if (!client) {
|
|
6
|
+
throw new Error('useHappiestChat must be used within a HappiestProvider');
|
|
7
|
+
}
|
|
8
|
+
const [state, setState] = useState(client.getState());
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const unsubscribe = client.subscribe(setState);
|
|
11
|
+
client.initSession();
|
|
12
|
+
return unsubscribe;
|
|
13
|
+
}, [client]);
|
|
14
|
+
return {
|
|
15
|
+
...state,
|
|
16
|
+
sendMessage: (text) => client.sendMessage(text),
|
|
17
|
+
retryMessage: (id) => client.retryMessage(id),
|
|
18
|
+
clearSession: () => client.clearSession(),
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
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.ChatPanel = ChatPanel;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const useHappiestChat_1 = require("../hooks/useHappiestChat");
|
|
40
|
+
const TypingIndicator = () => {
|
|
41
|
+
const anim1 = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
42
|
+
const anim2 = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
43
|
+
const anim3 = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
44
|
+
(0, react_1.useEffect)(() => {
|
|
45
|
+
const animateDot = (anim, delay) => {
|
|
46
|
+
react_native_1.Animated.loop(react_native_1.Animated.sequence([
|
|
47
|
+
react_native_1.Animated.delay(delay),
|
|
48
|
+
react_native_1.Animated.timing(anim, { toValue: 1, duration: 350, useNativeDriver: true }),
|
|
49
|
+
react_native_1.Animated.timing(anim, { toValue: 0, duration: 350, useNativeDriver: true }),
|
|
50
|
+
react_native_1.Animated.delay(700),
|
|
51
|
+
])).start();
|
|
52
|
+
};
|
|
53
|
+
animateDot(anim1, 0);
|
|
54
|
+
animateDot(anim2, 160);
|
|
55
|
+
animateDot(anim3, 320);
|
|
56
|
+
}, []);
|
|
57
|
+
return (<react_native_1.View style={styles.typingBubble}>
|
|
58
|
+
<react_native_1.Animated.View style={[styles.typingDot, { transform: [{ scale: anim1 }] }]}/>
|
|
59
|
+
<react_native_1.Animated.View style={[styles.typingDot, { transform: [{ scale: anim2 }] }]}/>
|
|
60
|
+
<react_native_1.Animated.View style={[styles.typingDot, { transform: [{ scale: anim3 }] }]}/>
|
|
61
|
+
</react_native_1.View>);
|
|
62
|
+
};
|
|
63
|
+
function ChatPanel({ style }) {
|
|
64
|
+
const { messages, isTyping, sendMessage, widgetConfig } = (0, useHappiestChat_1.useHappiestChat)();
|
|
65
|
+
const [input, setInput] = (0, react_1.useState)('');
|
|
66
|
+
const flatListRef = (0, react_1.useRef)(null);
|
|
67
|
+
const safeConfig = widgetConfig || {};
|
|
68
|
+
const primaryColor = safeConfig.theme?.primary || safeConfig.primaryColor || '#0A65CC';
|
|
69
|
+
const iconUrl = safeConfig.iconUrl;
|
|
70
|
+
const agentName = safeConfig.agentName || 'Chat Assistant';
|
|
71
|
+
const clubName = safeConfig.clubName || 'Club';
|
|
72
|
+
const welcomeMessage = safeConfig.welcomeMessage || 'Hi there! How can I help you today?';
|
|
73
|
+
const handleSend = () => {
|
|
74
|
+
if (!input.trim())
|
|
75
|
+
return;
|
|
76
|
+
sendMessage(input);
|
|
77
|
+
setInput('');
|
|
78
|
+
};
|
|
79
|
+
const renderItem = ({ item }) => {
|
|
80
|
+
const isUser = item.isUser;
|
|
81
|
+
return (<react_native_1.View style={[styles.messageWrapper, isUser ? styles.messageWrapperUser : styles.messageWrapperAgent]}>
|
|
82
|
+
<react_native_1.View style={[
|
|
83
|
+
styles.messageBubble,
|
|
84
|
+
isUser ? { backgroundColor: primaryColor, borderBottomRightRadius: 4 } : { ...styles.messageBubbleAgent, borderBottomLeftRadius: 4 }
|
|
85
|
+
]}>
|
|
86
|
+
<react_native_1.Text style={isUser ? styles.messageTextUser : styles.messageTextAgent}>
|
|
87
|
+
{item.text}
|
|
88
|
+
</react_native_1.Text>
|
|
89
|
+
</react_native_1.View>
|
|
90
|
+
</react_native_1.View>);
|
|
91
|
+
};
|
|
92
|
+
const renderHeader = () => (<react_native_1.View style={styles.welcomeWrapper}>
|
|
93
|
+
<react_native_1.View style={[styles.messageBubble, styles.messageBubbleAgent, { borderBottomLeftRadius: 4 }]}>
|
|
94
|
+
<react_native_1.Text style={styles.messageTextAgent}>{welcomeMessage}</react_native_1.Text>
|
|
95
|
+
</react_native_1.View>
|
|
96
|
+
</react_native_1.View>);
|
|
97
|
+
return (<react_native_1.SafeAreaView style={[styles.safeArea, { backgroundColor: primaryColor }, style]}>
|
|
98
|
+
<react_native_1.KeyboardAvoidingView style={styles.container} behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
99
|
+
{/* Header */}
|
|
100
|
+
<react_native_1.View style={[styles.header, { backgroundColor: primaryColor }]}>
|
|
101
|
+
<react_native_1.View style={styles.headerInfo}>
|
|
102
|
+
{iconUrl ? (<react_native_1.Image source={{ uri: iconUrl }} style={styles.headerIcon} resizeMode="cover"/>) : (<react_native_1.View style={styles.headerIconPlaceholder}>
|
|
103
|
+
<react_native_1.Text style={styles.headerIconText}>{agentName.charAt(0)}</react_native_1.Text>
|
|
104
|
+
</react_native_1.View>)}
|
|
105
|
+
<react_native_1.View>
|
|
106
|
+
<react_native_1.Text style={styles.headerTitle}>{agentName}</react_native_1.Text>
|
|
107
|
+
<react_native_1.Text style={styles.headerSubtitle}>Powered by Happiest for {clubName}</react_native_1.Text>
|
|
108
|
+
</react_native_1.View>
|
|
109
|
+
</react_native_1.View>
|
|
110
|
+
</react_native_1.View>
|
|
111
|
+
|
|
112
|
+
{/* Message List */}
|
|
113
|
+
<react_native_1.FlatList ref={flatListRef} data={messages} keyExtractor={(item) => item.id} renderItem={renderItem} ListHeaderComponent={renderHeader} contentContainerStyle={styles.listContent} onContentSizeChange={() => flatListRef.current?.scrollToEnd({ animated: true })} onLayout={() => flatListRef.current?.scrollToEnd({ animated: true })} ListFooterComponent={() => isTyping ? <TypingIndicator /> : <react_native_1.View style={{ height: 16 }}/>}/>
|
|
114
|
+
|
|
115
|
+
{/* Input Area */}
|
|
116
|
+
<react_native_1.View style={styles.inputContainer}>
|
|
117
|
+
<react_native_1.TextInput style={styles.input} value={input} onChangeText={setInput} placeholder="Type your message..." placeholderTextColor="#999" returnKeyType="send" onSubmitEditing={handleSend} underlineColorAndroid="transparent"/>
|
|
118
|
+
<react_native_1.TouchableOpacity style={[styles.sendButton, { backgroundColor: input.trim() ? primaryColor : '#D1D5DB' }]} onPress={handleSend} disabled={!input.trim()}>
|
|
119
|
+
<react_native_1.Text style={styles.sendButtonArrow}>➤</react_native_1.Text>
|
|
120
|
+
</react_native_1.TouchableOpacity>
|
|
121
|
+
</react_native_1.View>
|
|
122
|
+
</react_native_1.KeyboardAvoidingView>
|
|
123
|
+
</react_native_1.SafeAreaView>);
|
|
124
|
+
}
|
|
125
|
+
const styles = react_native_1.StyleSheet.create({
|
|
126
|
+
safeArea: {
|
|
127
|
+
flex: 1,
|
|
128
|
+
},
|
|
129
|
+
container: {
|
|
130
|
+
flex: 1,
|
|
131
|
+
backgroundColor: '#F9FAFB',
|
|
132
|
+
},
|
|
133
|
+
header: {
|
|
134
|
+
flexDirection: 'row',
|
|
135
|
+
alignItems: 'center',
|
|
136
|
+
padding: 20,
|
|
137
|
+
paddingTop: react_native_1.Platform.OS === 'ios' ? 20 : 40,
|
|
138
|
+
justifyContent: 'space-between',
|
|
139
|
+
},
|
|
140
|
+
headerInfo: {
|
|
141
|
+
flexDirection: 'row',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
},
|
|
144
|
+
headerIcon: {
|
|
145
|
+
width: 32,
|
|
146
|
+
height: 32,
|
|
147
|
+
borderRadius: 16,
|
|
148
|
+
marginRight: 12,
|
|
149
|
+
backgroundColor: '#FFF',
|
|
150
|
+
},
|
|
151
|
+
headerIconPlaceholder: {
|
|
152
|
+
width: 32,
|
|
153
|
+
height: 32,
|
|
154
|
+
borderRadius: 16,
|
|
155
|
+
marginRight: 12,
|
|
156
|
+
backgroundColor: 'rgba(255,255,255,0.2)',
|
|
157
|
+
alignItems: 'center',
|
|
158
|
+
justifyContent: 'center',
|
|
159
|
+
},
|
|
160
|
+
headerIconText: {
|
|
161
|
+
color: '#FFF',
|
|
162
|
+
fontSize: 16,
|
|
163
|
+
fontWeight: 'bold',
|
|
164
|
+
},
|
|
165
|
+
headerTitle: {
|
|
166
|
+
color: '#FFF',
|
|
167
|
+
fontSize: 16,
|
|
168
|
+
fontWeight: '600',
|
|
169
|
+
},
|
|
170
|
+
headerSubtitle: {
|
|
171
|
+
color: 'rgba(255,255,255,0.9)',
|
|
172
|
+
fontSize: 13,
|
|
173
|
+
marginTop: 2,
|
|
174
|
+
},
|
|
175
|
+
listContent: {
|
|
176
|
+
padding: 20,
|
|
177
|
+
paddingBottom: 8,
|
|
178
|
+
},
|
|
179
|
+
welcomeWrapper: {
|
|
180
|
+
marginBottom: 12,
|
|
181
|
+
flexDirection: 'row',
|
|
182
|
+
justifyContent: 'flex-start',
|
|
183
|
+
},
|
|
184
|
+
messageWrapper: {
|
|
185
|
+
marginBottom: 12,
|
|
186
|
+
flexDirection: 'row',
|
|
187
|
+
},
|
|
188
|
+
messageWrapperUser: {
|
|
189
|
+
justifyContent: 'flex-end',
|
|
190
|
+
},
|
|
191
|
+
messageWrapperAgent: {
|
|
192
|
+
justifyContent: 'flex-start',
|
|
193
|
+
},
|
|
194
|
+
messageBubble: {
|
|
195
|
+
maxWidth: '85%',
|
|
196
|
+
paddingHorizontal: 16,
|
|
197
|
+
paddingVertical: 12,
|
|
198
|
+
borderRadius: 12,
|
|
199
|
+
},
|
|
200
|
+
messageBubbleAgent: {
|
|
201
|
+
backgroundColor: '#FFFFFF',
|
|
202
|
+
borderWidth: 1,
|
|
203
|
+
borderColor: '#E5E7EB',
|
|
204
|
+
},
|
|
205
|
+
messageTextUser: {
|
|
206
|
+
color: '#FFFFFF',
|
|
207
|
+
fontSize: 14,
|
|
208
|
+
lineHeight: 20,
|
|
209
|
+
},
|
|
210
|
+
messageTextAgent: {
|
|
211
|
+
color: '#1F2937',
|
|
212
|
+
fontSize: 14,
|
|
213
|
+
lineHeight: 20,
|
|
214
|
+
},
|
|
215
|
+
typingBubble: {
|
|
216
|
+
alignSelf: 'flex-start',
|
|
217
|
+
backgroundColor: '#FFFFFF',
|
|
218
|
+
borderWidth: 1,
|
|
219
|
+
borderColor: '#E5E7EB',
|
|
220
|
+
paddingHorizontal: 16,
|
|
221
|
+
paddingVertical: 14,
|
|
222
|
+
borderRadius: 12,
|
|
223
|
+
borderBottomLeftRadius: 4,
|
|
224
|
+
flexDirection: 'row',
|
|
225
|
+
alignItems: 'center',
|
|
226
|
+
marginBottom: 16,
|
|
227
|
+
},
|
|
228
|
+
typingDot: {
|
|
229
|
+
width: 6,
|
|
230
|
+
height: 6,
|
|
231
|
+
backgroundColor: '#666666',
|
|
232
|
+
borderRadius: 3,
|
|
233
|
+
marginHorizontal: 2,
|
|
234
|
+
},
|
|
235
|
+
inputContainer: {
|
|
236
|
+
flexDirection: 'row',
|
|
237
|
+
padding: 16,
|
|
238
|
+
backgroundColor: '#FFFFFF',
|
|
239
|
+
borderTopWidth: 1,
|
|
240
|
+
borderTopColor: '#E5E7EB',
|
|
241
|
+
alignItems: 'center',
|
|
242
|
+
},
|
|
243
|
+
input: {
|
|
244
|
+
flex: 1,
|
|
245
|
+
minHeight: 40,
|
|
246
|
+
maxHeight: 100,
|
|
247
|
+
backgroundColor: '#FFFFFF',
|
|
248
|
+
borderWidth: 1,
|
|
249
|
+
borderColor: '#E5E7EB',
|
|
250
|
+
borderRadius: 24,
|
|
251
|
+
paddingHorizontal: 16,
|
|
252
|
+
paddingVertical: 10,
|
|
253
|
+
marginRight: 12,
|
|
254
|
+
fontSize: 14,
|
|
255
|
+
color: '#1F2937',
|
|
256
|
+
},
|
|
257
|
+
sendButton: {
|
|
258
|
+
width: 40,
|
|
259
|
+
height: 40,
|
|
260
|
+
borderRadius: 20,
|
|
261
|
+
justifyContent: 'center',
|
|
262
|
+
alignItems: 'center',
|
|
263
|
+
},
|
|
264
|
+
sendButtonArrow: {
|
|
265
|
+
color: '#FFFFFF',
|
|
266
|
+
fontSize: 16,
|
|
267
|
+
marginLeft: 2,
|
|
268
|
+
marginTop: -2,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { HappiestConfig } from '@happiest/chat-core';
|
|
3
|
+
export declare const HappiestContext: React.Context<any>;
|
|
4
|
+
export interface HappiestProviderProps {
|
|
5
|
+
config: HappiestConfig;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function HappiestProvider({ config, children }: HappiestProviderProps): React.JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
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.HappiestContext = void 0;
|
|
37
|
+
exports.HappiestProvider = HappiestProvider;
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const chat_core_1 = require("@happiest/chat-core");
|
|
40
|
+
exports.HappiestContext = (0, react_1.createContext)(null);
|
|
41
|
+
function HappiestProvider({ config, children }) {
|
|
42
|
+
const clientRef = (0, react_1.useRef)(null);
|
|
43
|
+
if (!clientRef.current) {
|
|
44
|
+
clientRef.current = new chat_core_1.HappiestClient(config);
|
|
45
|
+
}
|
|
46
|
+
return (<exports.HappiestContext.Provider value={clientRef.current}>
|
|
47
|
+
{children}
|
|
48
|
+
</exports.HappiestContext.Provider>);
|
|
49
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useHappiestChat = useHappiestChat;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const HappiestProvider_1 = require("../components/HappiestProvider");
|
|
6
|
+
function useHappiestChat() {
|
|
7
|
+
const client = (0, react_1.useContext)(HappiestProvider_1.HappiestContext);
|
|
8
|
+
if (!client) {
|
|
9
|
+
throw new Error('useHappiestChat must be used within a HappiestProvider');
|
|
10
|
+
}
|
|
11
|
+
const [state, setState] = (0, react_1.useState)(client.getState());
|
|
12
|
+
(0, react_1.useEffect)(() => {
|
|
13
|
+
const unsubscribe = client.subscribe(setState);
|
|
14
|
+
client.initSession();
|
|
15
|
+
return unsubscribe;
|
|
16
|
+
}, [client]);
|
|
17
|
+
return {
|
|
18
|
+
...state,
|
|
19
|
+
sendMessage: (text) => client.sendMessage(text),
|
|
20
|
+
clearSession: () => client.clearSession(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useHappiestChat = exports.ChatPanel = exports.HappiestContext = exports.HappiestProvider = void 0;
|
|
4
|
+
var HappiestProvider_1 = require("./components/HappiestProvider");
|
|
5
|
+
Object.defineProperty(exports, "HappiestProvider", { enumerable: true, get: function () { return HappiestProvider_1.HappiestProvider; } });
|
|
6
|
+
Object.defineProperty(exports, "HappiestContext", { enumerable: true, get: function () { return HappiestProvider_1.HappiestContext; } });
|
|
7
|
+
var ChatPanel_1 = require("./components/ChatPanel");
|
|
8
|
+
Object.defineProperty(exports, "ChatPanel", { enumerable: true, get: function () { return ChatPanel_1.ChatPanel; } });
|
|
9
|
+
var useHappiestChat_1 = require("./hooks/useHappiestChat");
|
|
10
|
+
Object.defineProperty(exports, "useHappiestChat", { enumerable: true, get: function () { return useHappiestChat_1.useHappiestChat; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happiest-team/ai-chat-native",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "React Native SDK for Happiest Chat",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"happiest",
|
|
12
|
+
"chat",
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"react-native",
|
|
16
|
+
"mobile"
|
|
17
|
+
],
|
|
18
|
+
"author": "Happiest",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"package.json"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@happiest-team/ai-chat-core": "*"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "*",
|
|
33
|
+
"react-native": "*"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "^19.2.17",
|
|
37
|
+
"@types/react-native": "^0.73.0",
|
|
38
|
+
"react": "^19.2.7",
|
|
39
|
+
"react-native": "^0.86.0",
|
|
40
|
+
"typescript": "^5.5.0"
|
|
41
|
+
}
|
|
42
|
+
}
|