@uselingu/react-native-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +279 -0
  2. package/lib/commonjs/components/LinguChat.js +283 -0
  3. package/lib/commonjs/components/LinguChat.js.map +1 -0
  4. package/lib/commonjs/components/LinguWebView.js +203 -0
  5. package/lib/commonjs/components/LinguWebView.js.map +1 -0
  6. package/lib/commonjs/components/MessageBubble.js +91 -0
  7. package/lib/commonjs/components/MessageBubble.js.map +1 -0
  8. package/lib/commonjs/components/NativeChatUI.js +192 -0
  9. package/lib/commonjs/components/NativeChatUI.js.map +1 -0
  10. package/lib/commonjs/components/TypingIndicator.js +92 -0
  11. package/lib/commonjs/components/TypingIndicator.js.map +1 -0
  12. package/lib/commonjs/context/LinguProvider.js +47 -0
  13. package/lib/commonjs/context/LinguProvider.js.map +1 -0
  14. package/lib/commonjs/context/index.js +17 -0
  15. package/lib/commonjs/context/index.js.map +1 -0
  16. package/lib/commonjs/hooks/useLinguChat.js +295 -0
  17. package/lib/commonjs/hooks/useLinguChat.js.map +1 -0
  18. package/lib/commonjs/index.js +129 -0
  19. package/lib/commonjs/index.js.map +1 -0
  20. package/lib/commonjs/services/LinguAPI.js +196 -0
  21. package/lib/commonjs/services/LinguAPI.js.map +1 -0
  22. package/lib/commonjs/services/SessionManager.js +131 -0
  23. package/lib/commonjs/services/SessionManager.js.map +1 -0
  24. package/lib/commonjs/services/StorageManager.js +116 -0
  25. package/lib/commonjs/services/StorageManager.js.map +1 -0
  26. package/lib/commonjs/theme/ThemeProvider.js +49 -0
  27. package/lib/commonjs/theme/ThemeProvider.js.map +1 -0
  28. package/lib/commonjs/theme/index.js +28 -0
  29. package/lib/commonjs/theme/index.js.map +1 -0
  30. package/lib/commonjs/theme/theme.js +150 -0
  31. package/lib/commonjs/theme/theme.js.map +1 -0
  32. package/lib/commonjs/types/index.js +2 -0
  33. package/lib/commonjs/types/index.js.map +1 -0
  34. package/lib/commonjs/utils/animations.js +118 -0
  35. package/lib/commonjs/utils/animations.js.map +1 -0
  36. package/lib/commonjs/utils/formatters.js +89 -0
  37. package/lib/commonjs/utils/formatters.js.map +1 -0
  38. package/lib/commonjs/utils/index.js +28 -0
  39. package/lib/commonjs/utils/index.js.map +1 -0
  40. package/lib/module/components/LinguChat.js +277 -0
  41. package/lib/module/components/LinguChat.js.map +1 -0
  42. package/lib/module/components/LinguWebView.js +195 -0
  43. package/lib/module/components/LinguWebView.js.map +1 -0
  44. package/lib/module/components/MessageBubble.js +83 -0
  45. package/lib/module/components/MessageBubble.js.map +1 -0
  46. package/lib/module/components/NativeChatUI.js +184 -0
  47. package/lib/module/components/NativeChatUI.js.map +1 -0
  48. package/lib/module/components/TypingIndicator.js +84 -0
  49. package/lib/module/components/TypingIndicator.js.map +1 -0
  50. package/lib/module/context/LinguProvider.js +38 -0
  51. package/lib/module/context/LinguProvider.js.map +1 -0
  52. package/lib/module/context/index.js +6 -0
  53. package/lib/module/context/index.js.map +1 -0
  54. package/lib/module/hooks/useLinguChat.js +288 -0
  55. package/lib/module/hooks/useLinguChat.js.map +1 -0
  56. package/lib/module/index.js +30 -0
  57. package/lib/module/index.js.map +1 -0
  58. package/lib/module/services/LinguAPI.js +190 -0
  59. package/lib/module/services/LinguAPI.js.map +1 -0
  60. package/lib/module/services/SessionManager.js +124 -0
  61. package/lib/module/services/SessionManager.js.map +1 -0
  62. package/lib/module/services/StorageManager.js +108 -0
  63. package/lib/module/services/StorageManager.js.map +1 -0
  64. package/lib/module/theme/ThemeProvider.js +40 -0
  65. package/lib/module/theme/ThemeProvider.js.map +1 -0
  66. package/lib/module/theme/index.js +7 -0
  67. package/lib/module/theme/index.js.map +1 -0
  68. package/lib/module/theme/theme.js +142 -0
  69. package/lib/module/theme/theme.js.map +1 -0
  70. package/lib/module/types/index.js +2 -0
  71. package/lib/module/types/index.js.map +1 -0
  72. package/lib/module/utils/animations.js +106 -0
  73. package/lib/module/utils/animations.js.map +1 -0
  74. package/lib/module/utils/formatters.js +78 -0
  75. package/lib/module/utils/formatters.js.map +1 -0
  76. package/lib/module/utils/index.js +7 -0
  77. package/lib/module/utils/index.js.map +1 -0
  78. package/lib/typescript/components/LinguChat.d.ts +18 -0
  79. package/lib/typescript/components/LinguChat.d.ts.map +1 -0
  80. package/lib/typescript/components/LinguWebView.d.ts +16 -0
  81. package/lib/typescript/components/LinguWebView.d.ts.map +1 -0
  82. package/lib/typescript/components/MessageBubble.d.ts +13 -0
  83. package/lib/typescript/components/MessageBubble.d.ts.map +1 -0
  84. package/lib/typescript/components/NativeChatUI.d.ts +16 -0
  85. package/lib/typescript/components/NativeChatUI.d.ts.map +1 -0
  86. package/lib/typescript/components/TypingIndicator.d.ts +7 -0
  87. package/lib/typescript/components/TypingIndicator.d.ts.map +1 -0
  88. package/lib/typescript/context/LinguProvider.d.ts +32 -0
  89. package/lib/typescript/context/LinguProvider.d.ts.map +1 -0
  90. package/lib/typescript/context/index.d.ts +5 -0
  91. package/lib/typescript/context/index.d.ts.map +1 -0
  92. package/lib/typescript/hooks/useLinguChat.d.ts +18 -0
  93. package/lib/typescript/hooks/useLinguChat.d.ts.map +1 -0
  94. package/lib/typescript/index.d.ts +19 -0
  95. package/lib/typescript/index.d.ts.map +1 -0
  96. package/lib/typescript/services/LinguAPI.d.ts +39 -0
  97. package/lib/typescript/services/LinguAPI.d.ts.map +1 -0
  98. package/lib/typescript/services/SessionManager.d.ts +46 -0
  99. package/lib/typescript/services/SessionManager.d.ts.map +1 -0
  100. package/lib/typescript/services/StorageManager.d.ts +36 -0
  101. package/lib/typescript/services/StorageManager.d.ts.map +1 -0
  102. package/lib/typescript/theme/ThemeProvider.d.ts +23 -0
  103. package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -0
  104. package/lib/typescript/theme/index.d.ts +6 -0
  105. package/lib/typescript/theme/index.d.ts.map +1 -0
  106. package/lib/typescript/theme/theme.d.ts +130 -0
  107. package/lib/typescript/theme/theme.d.ts.map +1 -0
  108. package/lib/typescript/types/index.d.ts +50 -0
  109. package/lib/typescript/types/index.d.ts.map +1 -0
  110. package/lib/typescript/utils/animations.d.ts +34 -0
  111. package/lib/typescript/utils/animations.d.ts.map +1 -0
  112. package/lib/typescript/utils/formatters.d.ts +25 -0
  113. package/lib/typescript/utils/formatters.d.ts.map +1 -0
  114. package/lib/typescript/utils/index.d.ts +6 -0
  115. package/lib/typescript/utils/index.d.ts.map +1 -0
  116. package/package.json +98 -0
  117. package/src/components/LinguChat.tsx +309 -0
  118. package/src/components/LinguWebView.tsx +216 -0
  119. package/src/components/MessageBubble.tsx +120 -0
  120. package/src/components/NativeChatUI.tsx +247 -0
  121. package/src/components/TypingIndicator.tsx +84 -0
  122. package/src/context/LinguProvider.tsx +53 -0
  123. package/src/context/index.ts +5 -0
  124. package/src/hooks/useLinguChat.ts +314 -0
  125. package/src/index.ts +43 -0
  126. package/src/services/LinguAPI.ts +214 -0
  127. package/src/services/SessionManager.ts +131 -0
  128. package/src/services/StorageManager.ts +111 -0
  129. package/src/theme/ThemeProvider.tsx +58 -0
  130. package/src/theme/index.ts +6 -0
  131. package/src/theme/theme.ts +254 -0
  132. package/src/types/index.ts +59 -0
  133. package/src/utils/animations.ts +141 -0
  134. package/src/utils/formatters.ts +80 -0
  135. package/src/utils/index.ts +6 -0
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Native Chat UI Component
3
+ * Pure React Native implementation without WebView
4
+ * Now with theme support and enhanced styling
5
+ */
6
+
7
+ import React, { useState, useRef, useEffect } from 'react';
8
+ import {
9
+ View,
10
+ Text,
11
+ TextInput,
12
+ TouchableOpacity,
13
+ FlatList,
14
+ StyleSheet,
15
+ KeyboardAvoidingView,
16
+ Platform,
17
+ } from 'react-native';
18
+ import { useTheme } from '../theme';
19
+ import { MessageBubble } from './MessageBubble';
20
+ import { TypingIndicator } from './TypingIndicator';
21
+ import type { Message, WidgetConfig } from '../types';
22
+
23
+ const LOG_PREFIX = '[NativeChatUI]';
24
+
25
+ interface NativeChatUIProps {
26
+ config: WidgetConfig | null;
27
+ messages: Message[];
28
+ isLoading: boolean;
29
+ onSendMessage: (message: string) => void;
30
+ }
31
+
32
+ export const NativeChatUI: React.FC<NativeChatUIProps> = ({
33
+ config,
34
+ messages,
35
+ isLoading,
36
+ onSendMessage,
37
+ }) => {
38
+ const { theme } = useTheme();
39
+ const [inputText, setInputText] = useState('');
40
+ const flatListRef = useRef<FlatList>(null);
41
+
42
+ console.log(`${LOG_PREFIX} Rendering with ${messages.length} messages`);
43
+
44
+ // Auto-scroll to bottom when new messages arrive
45
+ useEffect(() => {
46
+ if (messages.length > 0 && flatListRef.current) {
47
+ setTimeout(() => {
48
+ flatListRef.current?.scrollToEnd({ animated: true });
49
+ }, 100);
50
+ }
51
+ }, [messages]);
52
+
53
+ const handleSend = () => {
54
+ if (inputText.trim()) {
55
+ console.log(`${LOG_PREFIX} Sending message:`, inputText);
56
+ onSendMessage(inputText.trim());
57
+ setInputText('');
58
+ }
59
+ };
60
+
61
+ const renderMessage = ({ item }: { item: Message }) => {
62
+ return <MessageBubble message={item} showTimestamp={false} />;
63
+ };
64
+
65
+ const renderEmpty = () => (
66
+ <View style={styles.emptyContainer}>
67
+ <Text
68
+ style={[
69
+ styles.greetingText,
70
+ {
71
+ color: theme.colors.gray500,
72
+ fontSize: theme.typography.fontSizes.base,
73
+ },
74
+ ]}
75
+ >
76
+ {config?.greetingMessage || 'Hello! How can I help you today?'}
77
+ </Text>
78
+ </View>
79
+ );
80
+
81
+ const renderFooter = () => {
82
+ if (isLoading) {
83
+ return <TypingIndicator />;
84
+ }
85
+ return null;
86
+ };
87
+
88
+ const charCount = inputText.length;
89
+ const maxChars = 5000;
90
+ const showCharCount = charCount > maxChars * 0.8;
91
+
92
+ return (
93
+ <KeyboardAvoidingView
94
+ style={[styles.container, { backgroundColor: theme.colors.background }]}
95
+ behavior={Platform.OS === 'ios' ? 'padding' : undefined}
96
+ keyboardVerticalOffset={Platform.OS === 'ios' ? 90 : 0}
97
+ >
98
+ {/* Messages List */}
99
+ <FlatList
100
+ ref={flatListRef}
101
+ data={messages}
102
+ renderItem={renderMessage}
103
+ keyExtractor={(item) => item.id}
104
+ contentContainerStyle={[
105
+ styles.messagesList,
106
+ { padding: theme.spacing.lg },
107
+ ]}
108
+ ListEmptyComponent={renderEmpty}
109
+ ListFooterComponent={renderFooter}
110
+ showsVerticalScrollIndicator={false}
111
+ />
112
+
113
+ {/* Input Area */}
114
+ <View
115
+ style={[
116
+ styles.inputContainer,
117
+ {
118
+ backgroundColor: theme.colors.background,
119
+ borderTopColor: theme.colors.inputBorder,
120
+ padding: theme.spacing.lg,
121
+ },
122
+ ]}
123
+ >
124
+ <TextInput
125
+ style={[
126
+ styles.input,
127
+ {
128
+ backgroundColor: theme.colors.inputBackground,
129
+ borderColor: theme.colors.inputBorder,
130
+ color: theme.colors.inputText,
131
+ fontSize: theme.typography.fontSizes.base,
132
+ borderRadius: theme.borderRadius.xl,
133
+ paddingHorizontal: theme.spacing.lg,
134
+ paddingVertical: theme.spacing.sm + 2,
135
+ marginRight: theme.spacing.sm,
136
+ },
137
+ ]}
138
+ value={inputText}
139
+ onChangeText={setInputText}
140
+ placeholder="Type a message..."
141
+ placeholderTextColor={theme.colors.gray400}
142
+ multiline
143
+ maxLength={maxChars}
144
+ onSubmitEditing={handleSend}
145
+ returnKeyType="send"
146
+ blurOnSubmit={false}
147
+ />
148
+ <TouchableOpacity
149
+ style={[
150
+ styles.sendButton,
151
+ {
152
+ backgroundColor: theme.colors.primary,
153
+ borderRadius: theme.borderRadius.xl,
154
+ paddingHorizontal: theme.spacing.lg,
155
+ paddingVertical: theme.spacing.sm + 2,
156
+ },
157
+ !inputText.trim() && styles.sendButtonDisabled,
158
+ ]}
159
+ onPress={handleSend}
160
+ disabled={!inputText.trim() || isLoading}
161
+ >
162
+ <Text
163
+ style={[
164
+ styles.sendButtonText,
165
+ {
166
+ fontSize: theme.typography.fontSizes.base,
167
+ fontWeight: theme.typography.fontWeights.semibold,
168
+ },
169
+ ]}
170
+ >
171
+ Send
172
+ </Text>
173
+ </TouchableOpacity>
174
+ </View>
175
+
176
+ {/* Character count */}
177
+ {showCharCount && (
178
+ <View
179
+ style={[
180
+ styles.charCountContainer,
181
+ { paddingHorizontal: theme.spacing.lg },
182
+ ]}
183
+ >
184
+ <Text
185
+ style={[
186
+ styles.charCount,
187
+ {
188
+ color: charCount >= maxChars ? theme.colors.errorText : theme.colors.gray500,
189
+ fontSize: theme.typography.fontSizes.xs,
190
+ },
191
+ ]}
192
+ >
193
+ {charCount}/{maxChars}
194
+ </Text>
195
+ </View>
196
+ )}
197
+ </KeyboardAvoidingView>
198
+ );
199
+ };
200
+
201
+ const styles = StyleSheet.create({
202
+ container: {
203
+ flex: 1,
204
+ },
205
+ messagesList: {
206
+ flexGrow: 1,
207
+ },
208
+ emptyContainer: {
209
+ flex: 1,
210
+ justifyContent: 'center',
211
+ alignItems: 'center',
212
+ padding: 32,
213
+ },
214
+ greetingText: {
215
+ textAlign: 'center',
216
+ },
217
+ inputContainer: {
218
+ flexDirection: 'row',
219
+ borderTopWidth: 1,
220
+ alignItems: 'flex-end',
221
+ },
222
+ input: {
223
+ flex: 1,
224
+ minHeight: 40,
225
+ maxHeight: 100,
226
+ borderWidth: 1,
227
+ },
228
+ sendButton: {
229
+ justifyContent: 'center',
230
+ alignItems: 'center',
231
+ minWidth: 70,
232
+ },
233
+ sendButtonDisabled: {
234
+ opacity: 0.5,
235
+ },
236
+ sendButtonText: {
237
+ color: '#ffffff',
238
+ },
239
+ charCountContainer: {
240
+ position: 'absolute',
241
+ bottom: 70,
242
+ right: 0,
243
+ },
244
+ charCount: {
245
+ textAlign: 'right',
246
+ },
247
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Typing Indicator Component
3
+ * Animated dots to show bot is typing
4
+ */
5
+
6
+ import React, { useEffect, useRef } from 'react';
7
+ import { View, Animated, StyleSheet } from 'react-native';
8
+ import { useTheme } from '../theme';
9
+ import { typingDotAnimation } from '../utils/animations';
10
+
11
+ export const TypingIndicator: React.FC = () => {
12
+ const { theme } = useTheme();
13
+
14
+ const dot1 = useRef(new Animated.Value(0)).current;
15
+ const dot2 = useRef(new Animated.Value(0)).current;
16
+ const dot3 = useRef(new Animated.Value(0)).current;
17
+
18
+ useEffect(() => {
19
+ // Start animations with staggered delays
20
+ const anim1 = typingDotAnimation(dot1, 0);
21
+ const anim2 = typingDotAnimation(dot2, 150);
22
+ const anim3 = typingDotAnimation(dot3, 300);
23
+
24
+ anim1.start();
25
+ anim2.start();
26
+ anim3.start();
27
+
28
+ return () => {
29
+ anim1.stop();
30
+ anim2.stop();
31
+ anim3.stop();
32
+ };
33
+ }, []);
34
+
35
+ return (
36
+ <View style={[styles.container, { backgroundColor: theme.colors.botBubble }]}>
37
+ <View style={styles.dotsContainer}>
38
+ <Animated.View
39
+ style={[
40
+ styles.dot,
41
+ { backgroundColor: theme.colors.gray400 },
42
+ { transform: [{ translateY: dot1 }] },
43
+ ]}
44
+ />
45
+ <Animated.View
46
+ style={[
47
+ styles.dot,
48
+ { backgroundColor: theme.colors.gray400 },
49
+ { transform: [{ translateY: dot2 }] },
50
+ ]}
51
+ />
52
+ <Animated.View
53
+ style={[
54
+ styles.dot,
55
+ { backgroundColor: theme.colors.gray400 },
56
+ { transform: [{ translateY: dot3 }] },
57
+ ]}
58
+ />
59
+ </View>
60
+ </View>
61
+ );
62
+ };
63
+
64
+ const styles = StyleSheet.create({
65
+ container: {
66
+ maxWidth: '75%',
67
+ padding: 12,
68
+ borderRadius: 16,
69
+ borderBottomLeftRadius: 4,
70
+ marginBottom: 12,
71
+ },
72
+ dotsContainer: {
73
+ flexDirection: 'row',
74
+ alignItems: 'center',
75
+ justifyContent: 'center',
76
+ height: 20,
77
+ },
78
+ dot: {
79
+ width: 8,
80
+ height: 8,
81
+ borderRadius: 4,
82
+ marginHorizontal: 3,
83
+ },
84
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Lingu SDK Context Provider
3
+ * Provides standalone state management for the SDK
4
+ * Completely isolated from host application state
5
+ */
6
+
7
+ import React, { createContext, useContext, ReactNode } from 'react';
8
+ import { useLinguChat } from '../hooks/useLinguChat';
9
+ import { ThemeProvider } from '../theme';
10
+ import type { Message, WidgetConfig, LinguConfig } from '../types';
11
+
12
+ interface LinguContextValue {
13
+ messages: Message[];
14
+ isLoading: boolean;
15
+ config: WidgetConfig | null;
16
+ sendMessage: (text: string) => Promise<void>;
17
+ startSession: () => Promise<void>;
18
+ endSession: () => Promise<void>;
19
+ isSessionActive: boolean;
20
+ clearHistory: () => Promise<void>;
21
+ }
22
+
23
+ const LinguContext = createContext<LinguContextValue | undefined>(undefined);
24
+
25
+ interface LinguProviderProps extends LinguConfig {
26
+ children: ReactNode;
27
+ }
28
+
29
+ /**
30
+ * LinguProvider - Wraps the SDK with standalone state management
31
+ * This ensures the SDK state is completely isolated from the host app
32
+ */
33
+ export const LinguProvider: React.FC<LinguProviderProps> = ({ children, ...linguConfig }) => {
34
+ const chatState = useLinguChat(linguConfig);
35
+
36
+ return (
37
+ <LinguContext.Provider value={chatState}>
38
+ <ThemeProvider config={chatState.config}>{children}</ThemeProvider>
39
+ </LinguContext.Provider>
40
+ );
41
+ };
42
+
43
+ /**
44
+ * Hook to access Lingu SDK context
45
+ * Must be used within a LinguProvider
46
+ */
47
+ export const useLinguContext = (): LinguContextValue => {
48
+ const context = useContext(LinguContext);
49
+ if (context === undefined) {
50
+ throw new Error('useLinguContext must be used within a LinguProvider');
51
+ }
52
+ return context;
53
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Context exports
3
+ */
4
+
5
+ export * from './LinguProvider';
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Custom hook for managing Lingu chat state and operations
3
+ * Now with message persistence and comprehensive logging
4
+ */
5
+
6
+ import { useState, useEffect, useCallback, useRef } from 'react';
7
+ import { LinguAPI } from '../services/LinguAPI';
8
+ import { SessionManager } from '../services/SessionManager';
9
+ import { StorageManager } from '../services/StorageManager';
10
+ import type { Message, WidgetConfig, LinguConfig } from '../types';
11
+
12
+ const LOG_PREFIX = '[useLinguChat]';
13
+
14
+ interface UseLinguChatReturn {
15
+ messages: Message[];
16
+ isLoading: boolean;
17
+ config: WidgetConfig | null;
18
+ sendMessage: (text: string) => Promise<void>;
19
+ startSession: () => Promise<void>;
20
+ endSession: () => Promise<void>;
21
+ isSessionActive: boolean;
22
+ clearHistory: () => Promise<void>;
23
+ }
24
+
25
+ export const useLinguChat = (linguConfig: LinguConfig): UseLinguChatReturn => {
26
+ console.log(`${LOG_PREFIX} Initializing hook with config:`, {
27
+ apiKey: linguConfig.apiKey?.substring(0, 20) + '...',
28
+ baseURL: linguConfig.baseURL,
29
+ autoOpen: linguConfig.autoOpen,
30
+ });
31
+
32
+ const [messages, setMessages] = useState<Message[]>([]);
33
+ const [isLoading, setIsLoading] = useState(false);
34
+ const [config, setConfig] = useState<WidgetConfig | null>(null);
35
+ const [isSessionActive, setIsSessionActive] = useState(false);
36
+
37
+ const apiRef = useRef<LinguAPI | null>(null);
38
+ const sessionManagerRef = useRef<SessionManager | null>(null);
39
+ const currentSessionIdRef = useRef<string | null>(null);
40
+
41
+ // Initialize API client and session manager
42
+ useEffect(() => {
43
+ console.log(`${LOG_PREFIX} Setting up API client and session manager`);
44
+
45
+ const baseURL = linguConfig.baseURL || 'https://api.uselingu.app/api';
46
+ console.log(`${LOG_PREFIX} API Base URL:`, baseURL);
47
+
48
+ const api = new LinguAPI(linguConfig.apiKey, baseURL);
49
+ apiRef.current = api;
50
+
51
+ const sessionManager = new SessionManager(api, {
52
+ onSessionStart: async (sessionId) => {
53
+ console.log(`${LOG_PREFIX} ✅ Session started:`, sessionId);
54
+ currentSessionIdRef.current = sessionId;
55
+ setIsSessionActive(true);
56
+
57
+ // Save session ID
58
+ await StorageManager.saveCurrentSessionId(sessionId);
59
+ console.log(`${LOG_PREFIX} Session ID saved to storage`);
60
+
61
+ // Try to load previous messages for this session
62
+ const savedMessages = await StorageManager.loadMessages(sessionId);
63
+ if (savedMessages.length > 0) {
64
+ console.log(`${LOG_PREFIX} Loaded ${savedMessages.length} messages from storage`);
65
+ setMessages(savedMessages);
66
+ } else {
67
+ console.log(`${LOG_PREFIX} No previous messages found for this session`);
68
+ }
69
+
70
+ linguConfig.onSessionStart?.(sessionId);
71
+ },
72
+ onSessionEnd: async (sessionId) => {
73
+ console.log(`${LOG_PREFIX} Session ending:`, sessionId);
74
+
75
+ // Save messages before ending
76
+ if (messages.length > 0) {
77
+ console.log(`${LOG_PREFIX} Saving ${messages.length} messages before session end`);
78
+ await StorageManager.saveMessages(sessionId, messages);
79
+ }
80
+
81
+ currentSessionIdRef.current = null;
82
+ setIsSessionActive(false);
83
+ console.log(`${LOG_PREFIX} ✅ Session ended successfully`);
84
+ linguConfig.onSessionEnd?.(sessionId);
85
+ },
86
+ });
87
+ sessionManagerRef.current = sessionManager;
88
+
89
+ // Load widget configuration
90
+ console.log(`${LOG_PREFIX} Fetching widget configuration...`);
91
+ api
92
+ .getConfig()
93
+ .then((fetchedConfig) => {
94
+ console.log(`${LOG_PREFIX} ✅ Widget config loaded:`, {
95
+ theme: fetchedConfig.theme,
96
+ primaryColor: fetchedConfig.primaryColor,
97
+ brandName: fetchedConfig.brandName,
98
+ greetingMessage: fetchedConfig.greetingMessage?.substring(0, 50),
99
+ });
100
+ setConfig(fetchedConfig);
101
+ })
102
+ .catch((error) => {
103
+ console.error(`${LOG_PREFIX} ❌ Failed to load config:`, error);
104
+ });
105
+
106
+ // Try to restore previous session
107
+ console.log(`${LOG_PREFIX} Attempting to restore previous session...`);
108
+ restorePreviousSession();
109
+
110
+ // Cleanup on unmount
111
+ return () => {
112
+ console.log(`${LOG_PREFIX} Component unmounting, cleaning up...`);
113
+ // Save messages before cleanup
114
+ if (currentSessionIdRef.current && messages.length > 0) {
115
+ console.log(`${LOG_PREFIX} Saving messages on unmount`);
116
+ StorageManager.saveMessages(currentSessionIdRef.current, messages);
117
+ }
118
+ sessionManagerRef.current?.cleanup();
119
+ console.log(`${LOG_PREFIX} Cleanup complete`);
120
+ };
121
+ }, [linguConfig.apiKey, linguConfig.baseURL]);
122
+
123
+ // Save messages whenever they change
124
+ useEffect(() => {
125
+ if (currentSessionIdRef.current && messages.length > 0) {
126
+ console.log(`${LOG_PREFIX} Auto-saving ${messages.length} messages`);
127
+ StorageManager.saveMessages(currentSessionIdRef.current, messages);
128
+ }
129
+ }, [messages]);
130
+
131
+ /**
132
+ * Try to restore previous session
133
+ */
134
+ const restorePreviousSession = async () => {
135
+ try {
136
+ const previousSessionId = await StorageManager.getCurrentSessionId();
137
+ if (previousSessionId) {
138
+ console.log(`${LOG_PREFIX} Found previous session:`, previousSessionId);
139
+ const savedMessages = await StorageManager.loadMessages(previousSessionId);
140
+ if (savedMessages.length > 0) {
141
+ console.log(`${LOG_PREFIX} ✅ Restored ${savedMessages.length} messages from previous session`);
142
+ setMessages(savedMessages);
143
+ currentSessionIdRef.current = previousSessionId;
144
+ } else {
145
+ console.log(`${LOG_PREFIX} Previous session had no messages`);
146
+ }
147
+ } else {
148
+ console.log(`${LOG_PREFIX} No previous session found`);
149
+ }
150
+ } catch (error) {
151
+ console.error(`${LOG_PREFIX} ❌ Failed to restore previous session:`, error);
152
+ }
153
+ };
154
+
155
+ /**
156
+ * Start a new chat session
157
+ */
158
+ const startSession = useCallback(async () => {
159
+ if (!sessionManagerRef.current) {
160
+ console.error(`${LOG_PREFIX} ❌ Session manager not initialized`);
161
+ return;
162
+ }
163
+
164
+ try {
165
+ console.log(`${LOG_PREFIX} Starting new session...`);
166
+ await sessionManagerRef.current.start();
167
+
168
+ // Add greeting message if configured and no previous messages
169
+ if (config?.greetingMessage && messages.length === 0) {
170
+ console.log(`${LOG_PREFIX} Adding greeting message`);
171
+ const greetingMsg: Message = {
172
+ id: `greeting-${Date.now()}`,
173
+ text: config.greetingMessage,
174
+ sender: 'bot',
175
+ timestamp: new Date(),
176
+ };
177
+ setMessages([greetingMsg]);
178
+ }
179
+ } catch (error) {
180
+ console.error(`${LOG_PREFIX} ❌ Failed to start session:`, error);
181
+ }
182
+ }, [config, messages.length]);
183
+
184
+ /**
185
+ * End the current chat session
186
+ */
187
+ const endSession = useCallback(async () => {
188
+ if (!sessionManagerRef.current) {
189
+ console.error(`${LOG_PREFIX} ❌ Session manager not initialized`);
190
+ return;
191
+ }
192
+
193
+ try {
194
+ console.log(`${LOG_PREFIX} Ending session...`);
195
+ await sessionManagerRef.current.end();
196
+ // Don't clear messages - they're saved in storage
197
+ console.log(`${LOG_PREFIX} ✅ Session ended, messages preserved`);
198
+ } catch (error) {
199
+ console.error(`${LOG_PREFIX} ❌ Failed to end session:`, error);
200
+ }
201
+ }, []);
202
+
203
+ /**
204
+ * Clear chat history
205
+ */
206
+ const clearHistory = useCallback(async () => {
207
+ console.log(`${LOG_PREFIX} Clearing chat history...`);
208
+ if (currentSessionIdRef.current) {
209
+ await StorageManager.clearMessages(currentSessionIdRef.current);
210
+ console.log(`${LOG_PREFIX} ✅ Storage cleared for session:`, currentSessionIdRef.current);
211
+ }
212
+ setMessages([]);
213
+ console.log(`${LOG_PREFIX} ✅ Messages cleared from state`);
214
+ }, []);
215
+
216
+ /**
217
+ * Send a message to the AI
218
+ */
219
+ const sendMessage = useCallback(
220
+ async (text: string) => {
221
+ if (!apiRef.current || !sessionManagerRef.current) {
222
+ console.error(`${LOG_PREFIX} ❌ API or session manager not initialized`);
223
+ return;
224
+ }
225
+
226
+ const sessionId = sessionManagerRef.current.getSessionId();
227
+ if (!sessionId) {
228
+ console.error(`${LOG_PREFIX} ❌ No active session`);
229
+ return;
230
+ }
231
+
232
+ console.log(`${LOG_PREFIX} Sending message:`, {
233
+ sessionId,
234
+ messageLength: text.length,
235
+ preview: text.substring(0, 50),
236
+ });
237
+
238
+ // Add user message
239
+ const userMessage: Message = {
240
+ id: `user-${Date.now()}`,
241
+ text,
242
+ sender: 'user',
243
+ timestamp: new Date(),
244
+ };
245
+ setMessages((prev) => {
246
+ console.log(`${LOG_PREFIX} Adding user message to state (total: ${prev.length + 1})`);
247
+ return [...prev, userMessage];
248
+ });
249
+ linguConfig.onMessageSent?.(text);
250
+
251
+ // Send to API
252
+ setIsLoading(true);
253
+ console.log(`${LOG_PREFIX} Waiting for AI response...`);
254
+ try {
255
+ const startTime = Date.now();
256
+ const response = await apiRef.current.sendMessage(text, sessionId);
257
+ const duration = Date.now() - startTime;
258
+
259
+ console.log(`${LOG_PREFIX} ✅ Received AI response in ${duration}ms:`, {
260
+ responseLength: response.length,
261
+ preview: response.substring(0, 50),
262
+ });
263
+
264
+ // Add bot response
265
+ const botMessage: Message = {
266
+ id: `bot-${Date.now()}`,
267
+ text: response,
268
+ sender: 'bot',
269
+ timestamp: new Date(),
270
+ };
271
+ setMessages((prev) => {
272
+ console.log(`${LOG_PREFIX} Adding bot message to state (total: ${prev.length + 1})`);
273
+ return [...prev, botMessage];
274
+ });
275
+ linguConfig.onMessageReceived?.(response);
276
+ } catch (error) {
277
+ console.error(`${LOG_PREFIX} ❌ Failed to send message:`, error);
278
+
279
+ // Add error message
280
+ const errorMessage: Message = {
281
+ id: `error-${Date.now()}`,
282
+ text: 'Sorry, I encountered an error. Please try again.',
283
+ sender: 'bot',
284
+ timestamp: new Date(),
285
+ };
286
+ setMessages((prev) => [...prev, errorMessage]);
287
+ console.log(`${LOG_PREFIX} Added error message to state`);
288
+ } finally {
289
+ setIsLoading(false);
290
+ console.log(`${LOG_PREFIX} Message send complete`);
291
+ }
292
+ },
293
+ [linguConfig]
294
+ );
295
+
296
+ console.log(`${LOG_PREFIX} Current state:`, {
297
+ messagesCount: messages.length,
298
+ isLoading,
299
+ hasConfig: !!config,
300
+ isSessionActive,
301
+ currentSessionId: currentSessionIdRef.current,
302
+ });
303
+
304
+ return {
305
+ messages,
306
+ isLoading,
307
+ config,
308
+ sendMessage,
309
+ startSession,
310
+ endSession,
311
+ isSessionActive,
312
+ clearHistory,
313
+ };
314
+ };