@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
package/src/index.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Lingu React Native SDK
3
+ * Standalone AI chat SDK with native UI and theme support
4
+ */
5
+
6
+ // Main components
7
+ export { LinguChat } from './components/LinguChat';
8
+ export { NativeChatUI } from './components/NativeChatUI';
9
+ export { LinguWebView } from './components/LinguWebView';
10
+ export { MessageBubble } from './components/MessageBubble';
11
+ export { TypingIndicator } from './components/TypingIndicator';
12
+
13
+ // Context and state management
14
+ export { LinguProvider, useLinguContext } from './context';
15
+
16
+ // Theme system
17
+ export { ThemeProvider, useTheme, createTheme } from './theme';
18
+ export type { Theme, ThemeColors } from './theme';
19
+
20
+ // Hooks
21
+ export { useLinguChat } from './hooks/useLinguChat';
22
+
23
+ // Services
24
+ export { LinguAPI } from './services/LinguAPI';
25
+ export { SessionManager } from './services/SessionManager';
26
+ export { StorageManager } from './services/StorageManager';
27
+
28
+ // Utils
29
+ export * from './utils';
30
+
31
+ // Types
32
+ export type {
33
+ LinguConfig,
34
+ ChatPosition,
35
+ ThemeMode,
36
+ Message,
37
+ MessageSender,
38
+ WidgetConfig,
39
+ SessionData,
40
+ ApiResponse,
41
+ ChatResponse,
42
+ SessionResponse,
43
+ } from './types';
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Lingu API Client
3
+ * Handles all communication with the Lingu backend API
4
+ * Now with comprehensive logging
5
+ */
6
+
7
+ import axios, { AxiosInstance, AxiosError } from 'axios';
8
+ import type {
9
+ WidgetConfig,
10
+ ChatResponse,
11
+ ApiResponse,
12
+ } from '../types';
13
+
14
+ const LOG_PREFIX = '[LinguAPI]';
15
+
16
+ export class LinguAPI {
17
+ private client: AxiosInstance;
18
+ private apiKey: string;
19
+ private sessionId: string | null = null;
20
+
21
+ constructor(apiKey: string, baseURL: string = 'https://api.uselingu.app/api') {
22
+ console.log(`${LOG_PREFIX} Initializing API client:`, {
23
+ baseURL,
24
+ apiKeyPrefix: apiKey.substring(0, 20) + '...',
25
+ });
26
+
27
+ this.apiKey = apiKey;
28
+ this.client = axios.create({
29
+ baseURL,
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ },
33
+ timeout: 30000, // 30 seconds
34
+ });
35
+
36
+ // Add request interceptor for logging
37
+ this.client.interceptors.request.use(
38
+ (config) => {
39
+ console.log(`${LOG_PREFIX} → Request:`, {
40
+ method: config.method?.toUpperCase(),
41
+ url: config.url,
42
+ hasData: !!config.data,
43
+ });
44
+ return config;
45
+ },
46
+ (error) => {
47
+ console.error(`${LOG_PREFIX} ❌ Request setup failed:`, error);
48
+ return Promise.reject(error);
49
+ }
50
+ );
51
+
52
+ // Add response interceptor for error handling and logging
53
+ this.client.interceptors.response.use(
54
+ (response) => {
55
+ console.log(`${LOG_PREFIX} ← Response:`, {
56
+ status: response.status,
57
+ url: response.config.url,
58
+ dataSize: JSON.stringify(response.data).length,
59
+ });
60
+ return response;
61
+ },
62
+ (error: AxiosError) => {
63
+ if (error.response) {
64
+ console.error(`${LOG_PREFIX} ❌ HTTP Error:`, {
65
+ status: error.response.status,
66
+ statusText: error.response.statusText,
67
+ url: error.config?.url,
68
+ data: error.response.data,
69
+ });
70
+ } else if (error.request) {
71
+ console.error(`${LOG_PREFIX} ❌ Network Error:`, {
72
+ message: error.message,
73
+ url: error.config?.url,
74
+ baseURL: error.config?.baseURL,
75
+ });
76
+ } else {
77
+ console.error(`${LOG_PREFIX} ❌ Request Error:`, error.message);
78
+ }
79
+ return Promise.reject(error);
80
+ }
81
+ );
82
+
83
+ console.log(`${LOG_PREFIX} ✅ API client initialized successfully`);
84
+ }
85
+
86
+ /**
87
+ * Get widget configuration from the API
88
+ */
89
+ async getConfig(): Promise<WidgetConfig> {
90
+ console.log(`${LOG_PREFIX} Fetching widget configuration...`);
91
+
92
+ try {
93
+ const startTime = Date.now();
94
+ const response = await this.client.get<ApiResponse<WidgetConfig>>(
95
+ '/chatbot-settings/widget/config',
96
+ {
97
+ headers: { 'X-API-Key': this.apiKey },
98
+ }
99
+ );
100
+ const duration = Date.now() - startTime;
101
+
102
+ console.log(`${LOG_PREFIX} ✅ Config loaded in ${duration}ms:`, {
103
+ theme: response.data.data.theme,
104
+ primaryColor: response.data.data.primaryColor,
105
+ brandName: response.data.data.brandName,
106
+ });
107
+
108
+ return response.data.data;
109
+ } catch (error) {
110
+ console.error(`${LOG_PREFIX} ❌ Failed to get config:`, error);
111
+ throw new Error('Failed to load widget configuration');
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Start a new chat session
117
+ * Note: Backend manages sessions automatically, we just generate a client-side ID
118
+ */
119
+ async startSession(): Promise<string> {
120
+ console.log(`${LOG_PREFIX} Starting new chat session (client-side)...`);
121
+
122
+ try {
123
+ // Generate a unique session ID client-side
124
+ const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(7)}`;
125
+ this.sessionId = sessionId;
126
+
127
+ console.log(`${LOG_PREFIX} ✅ Session created:`, sessionId);
128
+
129
+ return this.sessionId;
130
+ } catch (error) {
131
+ console.error(`${LOG_PREFIX} ❌ Failed to start session:`, error);
132
+ throw new Error('Failed to start chat session');
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Send a message to the AI
138
+ */
139
+ async sendMessage(message: string, sessionId: string): Promise<string> {
140
+ console.log(`${LOG_PREFIX} Sending message:`, {
141
+ sessionId,
142
+ messageLength: message.length,
143
+ preview: message.substring(0, 50) + (message.length > 50 ? '...' : ''),
144
+ });
145
+
146
+ try {
147
+ const startTime = Date.now();
148
+ const response = await this.client.post<ApiResponse<ChatResponse>>(
149
+ '/chat/widget',
150
+ {
151
+ apiKey: this.apiKey,
152
+ message,
153
+ sessionId,
154
+ }
155
+ );
156
+ const duration = Date.now() - startTime;
157
+
158
+ const aiResponse = response.data.data.message;
159
+ console.log(`${LOG_PREFIX} ✅ AI response received in ${duration}ms:`, {
160
+ responseLength: aiResponse.length,
161
+ preview: aiResponse.substring(0, 50) + (aiResponse.length > 50 ? '...' : ''),
162
+ });
163
+
164
+ return aiResponse;
165
+ } catch (error) {
166
+ console.error(`${LOG_PREFIX} ❌ Failed to send message:`, error);
167
+ throw new Error('Failed to send message');
168
+ }
169
+ }
170
+
171
+ /**
172
+ * End the current chat session
173
+ * Note: Backend doesn't have explicit session end, sessions timeout automatically
174
+ */
175
+ async endSession(sessionId: string): Promise<void> {
176
+ console.log(`${LOG_PREFIX} Ending session (client-side only):`, sessionId);
177
+
178
+ try {
179
+ this.sessionId = null;
180
+ console.log(`${LOG_PREFIX} ✅ Session ended successfully`);
181
+ } catch (error) {
182
+ console.error(`${LOG_PREFIX} ⚠️ Failed to end session:`, error);
183
+ // Don't throw - session will timeout anyway
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Send heartbeat to keep session alive
189
+ */
190
+ async sendHeartbeat(sessionId: string): Promise<void> {
191
+ console.log(`${LOG_PREFIX} Sending heartbeat for session:`, sessionId);
192
+
193
+ try {
194
+ // Send special heartbeat message
195
+ await this.client.post('/chat/widget', {
196
+ apiKey: this.apiKey,
197
+ message: '__HEARTBEAT__',
198
+ sessionId,
199
+ });
200
+ console.log(`${LOG_PREFIX} ✅ Heartbeat sent successfully`);
201
+ } catch (error) {
202
+ console.error(`${LOG_PREFIX} ⚠️ Heartbeat failed (will retry):`, error);
203
+ // Don't throw - will retry on next heartbeat
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Get the current session ID
209
+ */
210
+ getSessionId(): string | null {
211
+ console.log(`${LOG_PREFIX} Current session ID:`, this.sessionId || 'none');
212
+ return this.sessionId;
213
+ }
214
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Session Manager
3
+ * Handles session lifecycle, heartbeat, and cleanup
4
+ */
5
+
6
+ import { LinguAPI } from './LinguAPI';
7
+
8
+ export class SessionManager {
9
+ private api: LinguAPI;
10
+ private sessionId: string | null = null;
11
+ private heartbeatInterval: NodeJS.Timeout | null = null;
12
+ private heartbeatIntervalMs: number = 30000; // 30 seconds
13
+
14
+ private onSessionStart?: (sessionId: string) => void;
15
+ private onSessionEnd?: (sessionId: string) => void;
16
+
17
+ constructor(
18
+ api: LinguAPI,
19
+ callbacks?: {
20
+ onSessionStart?: (sessionId: string) => void;
21
+ onSessionEnd?: (sessionId: string) => void;
22
+ }
23
+ ) {
24
+ this.api = api;
25
+ this.onSessionStart = callbacks?.onSessionStart;
26
+ this.onSessionEnd = callbacks?.onSessionEnd;
27
+ }
28
+
29
+ /**
30
+ * Start a new session
31
+ */
32
+ async start(): Promise<string> {
33
+ try {
34
+ // End existing session if any
35
+ if (this.sessionId) {
36
+ await this.end();
37
+ }
38
+
39
+ // Start new session
40
+ this.sessionId = await this.api.startSession();
41
+
42
+ // Start heartbeat
43
+ this.startHeartbeat();
44
+
45
+ // Notify callback
46
+ this.onSessionStart?.(this.sessionId);
47
+
48
+ return this.sessionId;
49
+ } catch (error) {
50
+ console.error('[SessionManager] Failed to start session:', error);
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * End the current session
57
+ */
58
+ async end(): Promise<void> {
59
+ if (!this.sessionId) return;
60
+
61
+ try {
62
+ // Stop heartbeat
63
+ this.stopHeartbeat();
64
+
65
+ // End session on server
66
+ await this.api.endSession(this.sessionId);
67
+
68
+ // Notify callback
69
+ this.onSessionEnd?.(this.sessionId);
70
+
71
+ this.sessionId = null;
72
+ } catch (error) {
73
+ console.error('[SessionManager] Failed to end session:', error);
74
+ // Still clear local session
75
+ this.sessionId = null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Start sending heartbeats to keep session alive
81
+ */
82
+ private startHeartbeat(): void {
83
+ if (this.heartbeatInterval) {
84
+ clearInterval(this.heartbeatInterval);
85
+ }
86
+
87
+ this.heartbeatInterval = setInterval(() => {
88
+ if (this.sessionId) {
89
+ this.api.sendHeartbeat(this.sessionId).catch((error) => {
90
+ console.error('[SessionManager] Heartbeat failed:', error);
91
+ });
92
+ }
93
+ }, this.heartbeatIntervalMs);
94
+ }
95
+
96
+ /**
97
+ * Stop sending heartbeats
98
+ */
99
+ private stopHeartbeat(): void {
100
+ if (this.heartbeatInterval) {
101
+ clearInterval(this.heartbeatInterval);
102
+ this.heartbeatInterval = null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Get the current session ID
108
+ */
109
+ getSessionId(): string | null {
110
+ return this.sessionId;
111
+ }
112
+
113
+ /**
114
+ * Check if a session is active
115
+ */
116
+ isActive(): boolean {
117
+ return this.sessionId !== null;
118
+ }
119
+
120
+ /**
121
+ * Cleanup on unmount
122
+ */
123
+ cleanup(): void {
124
+ this.stopHeartbeat();
125
+ if (this.sessionId) {
126
+ // Fire and forget - don't wait for response
127
+ this.api.endSession(this.sessionId).catch(() => { });
128
+ this.sessionId = null;
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Storage Manager
3
+ * Handles message persistence using AsyncStorage
4
+ */
5
+
6
+ import AsyncStorage from '@react-native-async-storage/async-storage';
7
+ import type { Message } from '../types';
8
+
9
+ const STORAGE_KEY_PREFIX = '@lingu_chat_';
10
+ const MAX_STORED_MESSAGES = 100; // Limit stored messages per session
11
+
12
+ export class StorageManager {
13
+ /**
14
+ * Save messages for a session
15
+ */
16
+ static async saveMessages(sessionId: string, messages: Message[]): Promise<void> {
17
+ try {
18
+ const key = `${STORAGE_KEY_PREFIX}${sessionId}`;
19
+ // Keep only the last MAX_STORED_MESSAGES
20
+ const messagesToStore = messages.slice(-MAX_STORED_MESSAGES);
21
+ await AsyncStorage.setItem(key, JSON.stringify(messagesToStore));
22
+ } catch (error) {
23
+ console.error('[StorageManager] Failed to save messages:', error);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Load messages for a session
29
+ */
30
+ static async loadMessages(sessionId: string): Promise<Message[]> {
31
+ try {
32
+ const key = `${STORAGE_KEY_PREFIX}${sessionId}`;
33
+ const data = await AsyncStorage.getItem(key);
34
+ if (data) {
35
+ const messages = JSON.parse(data);
36
+ // Convert timestamp strings back to Date objects
37
+ return messages.map((msg: any) => ({
38
+ ...msg,
39
+ timestamp: new Date(msg.timestamp),
40
+ }));
41
+ }
42
+ return [];
43
+ } catch (error) {
44
+ console.error('[StorageManager] Failed to load messages:', error);
45
+ return [];
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Clear messages for a session
51
+ */
52
+ static async clearMessages(sessionId: string): Promise<void> {
53
+ try {
54
+ const key = `${STORAGE_KEY_PREFIX}${sessionId}`;
55
+ await AsyncStorage.removeItem(key);
56
+ } catch (error) {
57
+ console.error('[StorageManager] Failed to clear messages:', error);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get all stored session IDs
63
+ */
64
+ static async getAllSessionIds(): Promise<string[]> {
65
+ try {
66
+ const keys = await AsyncStorage.getAllKeys();
67
+ return keys
68
+ .filter((key) => key.startsWith(STORAGE_KEY_PREFIX))
69
+ .map((key) => key.replace(STORAGE_KEY_PREFIX, ''));
70
+ } catch (error) {
71
+ console.error('[StorageManager] Failed to get session IDs:', error);
72
+ return [];
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Save current session ID
78
+ */
79
+ static async saveCurrentSessionId(sessionId: string): Promise<void> {
80
+ try {
81
+ await AsyncStorage.setItem(`${STORAGE_KEY_PREFIX}current`, sessionId);
82
+ } catch (error) {
83
+ console.error('[StorageManager] Failed to save current session ID:', error);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get current session ID
89
+ */
90
+ static async getCurrentSessionId(): Promise<string | null> {
91
+ try {
92
+ return await AsyncStorage.getItem(`${STORAGE_KEY_PREFIX}current`);
93
+ } catch (error) {
94
+ console.error('[StorageManager] Failed to get current session ID:', error);
95
+ return null;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Clear all chat data
101
+ */
102
+ static async clearAll(): Promise<void> {
103
+ try {
104
+ const keys = await AsyncStorage.getAllKeys();
105
+ const chatKeys = keys.filter((key) => key.startsWith(STORAGE_KEY_PREFIX));
106
+ await AsyncStorage.multiRemove(chatKeys);
107
+ } catch (error) {
108
+ console.error('[StorageManager] Failed to clear all data:', error);
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Theme Provider
3
+ * Provides theme context to all child components
4
+ * Theme is derived from backend widget configuration
5
+ */
6
+
7
+ import React, { createContext, useContext, useMemo, ReactNode } from 'react';
8
+ import { createTheme, Theme } from './theme';
9
+ import type { WidgetConfig } from '../types';
10
+
11
+ interface ThemeContextValue {
12
+ theme: Theme;
13
+ config: WidgetConfig | null;
14
+ }
15
+
16
+ const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
17
+
18
+ interface ThemeProviderProps {
19
+ config: WidgetConfig | null;
20
+ children: ReactNode;
21
+ }
22
+
23
+ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ config, children }) => {
24
+ const theme = useMemo(() => {
25
+ if (!config) {
26
+ // Return default theme if config not loaded yet
27
+ return createTheme();
28
+ }
29
+
30
+ return createTheme(
31
+ config.primaryColor,
32
+ config.backgroundColor,
33
+ config.textColor,
34
+ config.theme === 'dark' ? 'dark' : 'light'
35
+ );
36
+ }, [config]);
37
+
38
+ const value = useMemo(
39
+ () => ({
40
+ theme,
41
+ config,
42
+ }),
43
+ [theme, config]
44
+ );
45
+
46
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
47
+ };
48
+
49
+ /**
50
+ * Hook to access theme context
51
+ */
52
+ export const useTheme = (): ThemeContextValue => {
53
+ const context = useContext(ThemeContext);
54
+ if (context === undefined) {
55
+ throw new Error('useTheme must be used within a ThemeProvider');
56
+ }
57
+ return context;
58
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Theme exports
3
+ */
4
+
5
+ export * from './theme';
6
+ export * from './ThemeProvider';