@chatwidgetai/chat-widget 0.1.1 → 0.1.2
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/api/client.d.ts +3 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/hooks/useChat.d.ts.map +1 -1
- package/dist/index.esm.js +99 -23
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +99 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/ai-chat-widget.umd.js +0 -65776
- package/dist/ai-chat-widget.umd.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -69,6 +69,19 @@ async function buildApiError(response, defaultMessage) {
|
|
|
69
69
|
class WidgetApiClient {
|
|
70
70
|
constructor(config) {
|
|
71
71
|
this.config = config;
|
|
72
|
+
this.detectedTimeZone = WidgetApiClient.detectTimeZone();
|
|
73
|
+
}
|
|
74
|
+
static detectTimeZone() {
|
|
75
|
+
try {
|
|
76
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
77
|
+
return tz && tz.trim().length > 0 ? tz : "UTC";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return "UTC";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
getTimeZone() {
|
|
84
|
+
return this.detectedTimeZone;
|
|
72
85
|
}
|
|
73
86
|
/**
|
|
74
87
|
* Get widget configuration
|
|
@@ -89,7 +102,15 @@ class WidgetApiClient {
|
|
|
89
102
|
}
|
|
90
103
|
async getOrCreateConversation(conversationId) {
|
|
91
104
|
const baseUrl = `${this.config.apiUrl}/api/widget/${this.config.widgetId}/conversation`;
|
|
92
|
-
const
|
|
105
|
+
const params = new URLSearchParams();
|
|
106
|
+
if (conversationId) {
|
|
107
|
+
params.set('conversationId', conversationId);
|
|
108
|
+
}
|
|
109
|
+
const timeZone = this.getTimeZone();
|
|
110
|
+
if (timeZone) {
|
|
111
|
+
params.set('timeZone', timeZone);
|
|
112
|
+
}
|
|
113
|
+
const query = params.toString() ? `?${params.toString()}` : '';
|
|
93
114
|
const response = await fetch(`${baseUrl}${query}`, {
|
|
94
115
|
method: 'GET',
|
|
95
116
|
headers: {
|
|
@@ -136,6 +157,7 @@ class WidgetApiClient {
|
|
|
136
157
|
conversationId: conversationId,
|
|
137
158
|
message,
|
|
138
159
|
fileIds,
|
|
160
|
+
timeZone: this.getTimeZone(),
|
|
139
161
|
}),
|
|
140
162
|
});
|
|
141
163
|
if (!response.ok) {
|
|
@@ -158,6 +180,7 @@ class WidgetApiClient {
|
|
|
158
180
|
conversationId: conversationId,
|
|
159
181
|
message,
|
|
160
182
|
fileIds,
|
|
183
|
+
timeZone: this.getTimeZone(),
|
|
161
184
|
}),
|
|
162
185
|
});
|
|
163
186
|
if (!response.ok) {
|
|
@@ -234,11 +257,6 @@ class WidgetApiClient {
|
|
|
234
257
|
/**
|
|
235
258
|
* Generate a unique session ID
|
|
236
259
|
*/
|
|
237
|
-
function generateSessionId() {
|
|
238
|
-
const timestamp = Date.now().toString(36);
|
|
239
|
-
const randomStr = Math.random().toString(36).substring(2, 15);
|
|
240
|
-
return `session_${timestamp}_${randomStr}`;
|
|
241
|
-
}
|
|
242
260
|
/**
|
|
243
261
|
* Generate a unique message ID
|
|
244
262
|
*/
|
|
@@ -293,6 +311,18 @@ function loadConversation(widgetId) {
|
|
|
293
311
|
return null;
|
|
294
312
|
}
|
|
295
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Clear conversation from localStorage
|
|
316
|
+
*/
|
|
317
|
+
function clearConversation(widgetId) {
|
|
318
|
+
try {
|
|
319
|
+
const key = getStorageKey(widgetId);
|
|
320
|
+
localStorage.removeItem(key);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
console.error('Failed to clear conversation:', error);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
296
326
|
/**
|
|
297
327
|
* Check if localStorage is available
|
|
298
328
|
*/
|
|
@@ -385,29 +415,43 @@ function useChat(options) {
|
|
|
385
415
|
config: null,
|
|
386
416
|
});
|
|
387
417
|
const apiClient = react.useRef(new WidgetApiClient({ widgetId, apiKey, apiUrl }));
|
|
388
|
-
// Load configuration and conversation
|
|
418
|
+
// Load configuration on mount and hydrate with existing conversation if available
|
|
389
419
|
react.useEffect(() => {
|
|
420
|
+
let isMounted = true;
|
|
390
421
|
const initialize = async () => {
|
|
391
422
|
try {
|
|
392
|
-
// Load config
|
|
393
423
|
const config = await apiClient.current.getConfig();
|
|
394
|
-
// Get or create conversation
|
|
395
424
|
const persistConversation = config.behavior.persistConversation ?? true;
|
|
396
|
-
let conversationId;
|
|
425
|
+
let conversationId = '';
|
|
426
|
+
let messages = [];
|
|
397
427
|
if (persistConversation && isStorageAvailable()) {
|
|
398
428
|
const stored = loadConversation(widgetId);
|
|
399
|
-
|
|
429
|
+
const storedId = stored?.conversationId || stored?.sessionId;
|
|
430
|
+
if (storedId) {
|
|
431
|
+
try {
|
|
432
|
+
const conversation = await apiClient.current.getOrCreateConversation(storedId);
|
|
433
|
+
conversationId = conversation.id;
|
|
434
|
+
messages = conversation.messages;
|
|
435
|
+
}
|
|
436
|
+
catch (conversationError) {
|
|
437
|
+
console.warn('Failed to load existing conversation:', conversationError);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (!isMounted) {
|
|
442
|
+
return;
|
|
400
443
|
}
|
|
401
|
-
// Get conversation from backend
|
|
402
|
-
const conversation = await apiClient.current.getOrCreateConversation(conversationId);
|
|
403
444
|
setState(prev => ({
|
|
404
445
|
...prev,
|
|
405
446
|
config,
|
|
406
|
-
conversationId
|
|
407
|
-
messages
|
|
447
|
+
conversationId,
|
|
448
|
+
messages,
|
|
408
449
|
}));
|
|
409
450
|
}
|
|
410
451
|
catch (error) {
|
|
452
|
+
if (!isMounted) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
411
455
|
const errorInfo = deriveErrorInfo(error);
|
|
412
456
|
const err = error instanceof Error ? error : new Error(errorInfo.message);
|
|
413
457
|
setState(prev => ({ ...prev, error: errorInfo.message }));
|
|
@@ -415,11 +459,17 @@ function useChat(options) {
|
|
|
415
459
|
}
|
|
416
460
|
};
|
|
417
461
|
initialize();
|
|
462
|
+
return () => {
|
|
463
|
+
isMounted = false;
|
|
464
|
+
};
|
|
418
465
|
}, [widgetId, apiKey, apiUrl, onError]);
|
|
419
466
|
// Save conversation when messages change
|
|
420
467
|
react.useEffect(() => {
|
|
421
468
|
const persistConversation = state.config?.behavior.persistConversation ?? true;
|
|
422
|
-
if (persistConversation &&
|
|
469
|
+
if (persistConversation &&
|
|
470
|
+
isStorageAvailable() &&
|
|
471
|
+
state.messages.length > 0 &&
|
|
472
|
+
state.conversationId) {
|
|
423
473
|
saveConversation(widgetId, state.conversationId, state.messages);
|
|
424
474
|
}
|
|
425
475
|
}, [widgetId, state.messages, state.conversationId, state.config?.behavior.persistConversation]);
|
|
@@ -427,13 +477,15 @@ function useChat(options) {
|
|
|
427
477
|
* Send a message
|
|
428
478
|
*/
|
|
429
479
|
const sendMessage = react.useCallback(async (content, files) => {
|
|
430
|
-
|
|
480
|
+
const trimmedContent = content.trim();
|
|
481
|
+
const hasFiles = !!files && files.length > 0;
|
|
482
|
+
if (!trimmedContent && !hasFiles)
|
|
431
483
|
return;
|
|
432
484
|
const userMessage = {
|
|
433
485
|
id: generateMessageId(),
|
|
434
486
|
message: {
|
|
435
487
|
type: 'human',
|
|
436
|
-
content:
|
|
488
|
+
content: trimmedContent,
|
|
437
489
|
},
|
|
438
490
|
timestamp: new Date().toISOString(),
|
|
439
491
|
sources: [],
|
|
@@ -448,13 +500,37 @@ function useChat(options) {
|
|
|
448
500
|
}));
|
|
449
501
|
onMessage?.(userMessage);
|
|
450
502
|
try {
|
|
503
|
+
let conversationId = state.conversationId;
|
|
504
|
+
if (!conversationId) {
|
|
505
|
+
const conversation = await apiClient.current.getOrCreateConversation();
|
|
506
|
+
conversationId = conversation.id;
|
|
507
|
+
setState(prev => {
|
|
508
|
+
const serverMessages = conversation.messages ?? [];
|
|
509
|
+
if (serverMessages.length === 0) {
|
|
510
|
+
return {
|
|
511
|
+
...prev,
|
|
512
|
+
conversationId,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const serverIds = new Set(serverMessages.map(msg => msg.id));
|
|
516
|
+
const mergedMessages = [
|
|
517
|
+
...serverMessages,
|
|
518
|
+
...prev.messages.filter(msg => !serverIds.has(msg.id)),
|
|
519
|
+
];
|
|
520
|
+
return {
|
|
521
|
+
...prev,
|
|
522
|
+
conversationId,
|
|
523
|
+
messages: mergedMessages,
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
}
|
|
451
527
|
// Upload files if provided
|
|
452
528
|
let fileIds;
|
|
453
529
|
if (files && files.length > 0) {
|
|
454
530
|
fileIds = [];
|
|
455
531
|
for (const file of files) {
|
|
456
532
|
try {
|
|
457
|
-
const uploadedFile = await apiClient.current.uploadFile(
|
|
533
|
+
const uploadedFile = await apiClient.current.uploadFile(conversationId, file);
|
|
458
534
|
fileIds.push(uploadedFile.id);
|
|
459
535
|
}
|
|
460
536
|
catch (uploadError) {
|
|
@@ -468,11 +544,11 @@ function useChat(options) {
|
|
|
468
544
|
let response;
|
|
469
545
|
if (useAgent) {
|
|
470
546
|
// Use agent endpoint - returns ConversationMessage[]
|
|
471
|
-
response = await apiClient.current.sendAgentMessage(
|
|
547
|
+
response = await apiClient.current.sendAgentMessage(conversationId, trimmedContent, fileIds);
|
|
472
548
|
}
|
|
473
549
|
else {
|
|
474
550
|
// Use standard chat endpoint
|
|
475
|
-
response = await apiClient.current.sendMessage(
|
|
551
|
+
response = await apiClient.current.sendMessage(conversationId, trimmedContent, fileIds);
|
|
476
552
|
}
|
|
477
553
|
// Both endpoints now return ConversationMessage[] array (FULL conversation)
|
|
478
554
|
if (Array.isArray(response)) {
|
|
@@ -584,12 +660,12 @@ function useChat(options) {
|
|
|
584
660
|
setState(prev => ({
|
|
585
661
|
...prev,
|
|
586
662
|
messages: [],
|
|
587
|
-
conversationId:
|
|
663
|
+
conversationId: '',
|
|
588
664
|
error: null,
|
|
589
665
|
}));
|
|
590
666
|
const persistConversation = state.config?.behavior.persistConversation ?? true;
|
|
591
667
|
if (persistConversation && isStorageAvailable()) {
|
|
592
|
-
|
|
668
|
+
clearConversation(widgetId);
|
|
593
669
|
}
|
|
594
670
|
}, [widgetId, state.config?.behavior.persistConversation]);
|
|
595
671
|
/**
|