@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.
@@ -19,7 +19,10 @@ export declare class ApiError extends Error {
19
19
  }
20
20
  export declare class WidgetApiClient {
21
21
  private config;
22
+ private detectedTimeZone;
22
23
  constructor(config: ApiClientConfig);
24
+ private static detectTimeZone;
25
+ private getTimeZone;
23
26
  /**
24
27
  * Get widget configuration
25
28
  */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;CAOpG;AAwDD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAkB;gBAEpB,MAAM,EAAE,eAAe;IAInC;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAoBlC,uBAAuB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC;IAqBhH;;OAEG;IACG,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAwB7G;;;OAGG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAwB9G;;OAEG;IACG,gBAAgB,CACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,GAAG,CAAC;IA2Cf;;OAEG;IACG,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,UAAU,GAAG,UAAU,GAChC,OAAO,CAAC,IAAI,CAAC;IA4BhB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAsBzC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;CAOpG;AAwDD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,gBAAgB,CAAS;gBAErB,MAAM,EAAE,eAAe;IAKnC,OAAO,CAAC,MAAM,CAAC,cAAc;IAS7B,OAAO,CAAC,WAAW;IAInB;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAoBlC,uBAAuB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC;IA6BhH;;OAEG;IACG,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAwB7G;;;OAGG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAyB9G;;OAEG;IACG,gBAAgB,CACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,GAAG,CAAC;IA4Cf;;OAEG;IACG,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,UAAU,GAAG,UAAU,GAChC,OAAO,CAAC,IAAI,CAAC;IA4BhB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAsBzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useChat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAa,MAAM,UAAU,CAAC;AAKxE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzF;AAkED,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CAyS9D"}
1
+ {"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/hooks/useChat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAa,MAAM,UAAU,CAAC;AAKxE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzF;AAkED,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CAiW9D"}
package/dist/index.esm.js CHANGED
@@ -67,6 +67,19 @@ async function buildApiError(response, defaultMessage) {
67
67
  class WidgetApiClient {
68
68
  constructor(config) {
69
69
  this.config = config;
70
+ this.detectedTimeZone = WidgetApiClient.detectTimeZone();
71
+ }
72
+ static detectTimeZone() {
73
+ try {
74
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
75
+ return tz && tz.trim().length > 0 ? tz : "UTC";
76
+ }
77
+ catch {
78
+ return "UTC";
79
+ }
80
+ }
81
+ getTimeZone() {
82
+ return this.detectedTimeZone;
70
83
  }
71
84
  /**
72
85
  * Get widget configuration
@@ -87,7 +100,15 @@ class WidgetApiClient {
87
100
  }
88
101
  async getOrCreateConversation(conversationId) {
89
102
  const baseUrl = `${this.config.apiUrl}/api/widget/${this.config.widgetId}/conversation`;
90
- const query = conversationId ? `?conversationId=${encodeURIComponent(conversationId)}` : '';
103
+ const params = new URLSearchParams();
104
+ if (conversationId) {
105
+ params.set('conversationId', conversationId);
106
+ }
107
+ const timeZone = this.getTimeZone();
108
+ if (timeZone) {
109
+ params.set('timeZone', timeZone);
110
+ }
111
+ const query = params.toString() ? `?${params.toString()}` : '';
91
112
  const response = await fetch(`${baseUrl}${query}`, {
92
113
  method: 'GET',
93
114
  headers: {
@@ -134,6 +155,7 @@ class WidgetApiClient {
134
155
  conversationId: conversationId,
135
156
  message,
136
157
  fileIds,
158
+ timeZone: this.getTimeZone(),
137
159
  }),
138
160
  });
139
161
  if (!response.ok) {
@@ -156,6 +178,7 @@ class WidgetApiClient {
156
178
  conversationId: conversationId,
157
179
  message,
158
180
  fileIds,
181
+ timeZone: this.getTimeZone(),
159
182
  }),
160
183
  });
161
184
  if (!response.ok) {
@@ -232,11 +255,6 @@ class WidgetApiClient {
232
255
  /**
233
256
  * Generate a unique session ID
234
257
  */
235
- function generateSessionId() {
236
- const timestamp = Date.now().toString(36);
237
- const randomStr = Math.random().toString(36).substring(2, 15);
238
- return `session_${timestamp}_${randomStr}`;
239
- }
240
258
  /**
241
259
  * Generate a unique message ID
242
260
  */
@@ -291,6 +309,18 @@ function loadConversation(widgetId) {
291
309
  return null;
292
310
  }
293
311
  }
312
+ /**
313
+ * Clear conversation from localStorage
314
+ */
315
+ function clearConversation(widgetId) {
316
+ try {
317
+ const key = getStorageKey(widgetId);
318
+ localStorage.removeItem(key);
319
+ }
320
+ catch (error) {
321
+ console.error('Failed to clear conversation:', error);
322
+ }
323
+ }
294
324
  /**
295
325
  * Check if localStorage is available
296
326
  */
@@ -383,29 +413,43 @@ function useChat(options) {
383
413
  config: null,
384
414
  });
385
415
  const apiClient = useRef(new WidgetApiClient({ widgetId, apiKey, apiUrl }));
386
- // Load configuration and conversation on mount
416
+ // Load configuration on mount and hydrate with existing conversation if available
387
417
  useEffect(() => {
418
+ let isMounted = true;
388
419
  const initialize = async () => {
389
420
  try {
390
- // Load config
391
421
  const config = await apiClient.current.getConfig();
392
- // Get or create conversation
393
422
  const persistConversation = config.behavior.persistConversation ?? true;
394
- let conversationId;
423
+ let conversationId = '';
424
+ let messages = [];
395
425
  if (persistConversation && isStorageAvailable()) {
396
426
  const stored = loadConversation(widgetId);
397
- conversationId = stored?.conversationId || stored?.sessionId; // Support old sessionId for backwards compatibility
427
+ const storedId = stored?.conversationId || stored?.sessionId;
428
+ if (storedId) {
429
+ try {
430
+ const conversation = await apiClient.current.getOrCreateConversation(storedId);
431
+ conversationId = conversation.id;
432
+ messages = conversation.messages;
433
+ }
434
+ catch (conversationError) {
435
+ console.warn('Failed to load existing conversation:', conversationError);
436
+ }
437
+ }
438
+ }
439
+ if (!isMounted) {
440
+ return;
398
441
  }
399
- // Get conversation from backend
400
- const conversation = await apiClient.current.getOrCreateConversation(conversationId);
401
442
  setState(prev => ({
402
443
  ...prev,
403
444
  config,
404
- conversationId: conversation.id,
405
- messages: conversation.messages,
445
+ conversationId,
446
+ messages,
406
447
  }));
407
448
  }
408
449
  catch (error) {
450
+ if (!isMounted) {
451
+ return;
452
+ }
409
453
  const errorInfo = deriveErrorInfo(error);
410
454
  const err = error instanceof Error ? error : new Error(errorInfo.message);
411
455
  setState(prev => ({ ...prev, error: errorInfo.message }));
@@ -413,11 +457,17 @@ function useChat(options) {
413
457
  }
414
458
  };
415
459
  initialize();
460
+ return () => {
461
+ isMounted = false;
462
+ };
416
463
  }, [widgetId, apiKey, apiUrl, onError]);
417
464
  // Save conversation when messages change
418
465
  useEffect(() => {
419
466
  const persistConversation = state.config?.behavior.persistConversation ?? true;
420
- if (persistConversation && isStorageAvailable() && state.messages.length > 0) {
467
+ if (persistConversation &&
468
+ isStorageAvailable() &&
469
+ state.messages.length > 0 &&
470
+ state.conversationId) {
421
471
  saveConversation(widgetId, state.conversationId, state.messages);
422
472
  }
423
473
  }, [widgetId, state.messages, state.conversationId, state.config?.behavior.persistConversation]);
@@ -425,13 +475,15 @@ function useChat(options) {
425
475
  * Send a message
426
476
  */
427
477
  const sendMessage = useCallback(async (content, files) => {
428
- if (!content.trim() && (!files || files.length === 0))
478
+ const trimmedContent = content.trim();
479
+ const hasFiles = !!files && files.length > 0;
480
+ if (!trimmedContent && !hasFiles)
429
481
  return;
430
482
  const userMessage = {
431
483
  id: generateMessageId(),
432
484
  message: {
433
485
  type: 'human',
434
- content: content.trim(),
486
+ content: trimmedContent,
435
487
  },
436
488
  timestamp: new Date().toISOString(),
437
489
  sources: [],
@@ -446,13 +498,37 @@ function useChat(options) {
446
498
  }));
447
499
  onMessage?.(userMessage);
448
500
  try {
501
+ let conversationId = state.conversationId;
502
+ if (!conversationId) {
503
+ const conversation = await apiClient.current.getOrCreateConversation();
504
+ conversationId = conversation.id;
505
+ setState(prev => {
506
+ const serverMessages = conversation.messages ?? [];
507
+ if (serverMessages.length === 0) {
508
+ return {
509
+ ...prev,
510
+ conversationId,
511
+ };
512
+ }
513
+ const serverIds = new Set(serverMessages.map(msg => msg.id));
514
+ const mergedMessages = [
515
+ ...serverMessages,
516
+ ...prev.messages.filter(msg => !serverIds.has(msg.id)),
517
+ ];
518
+ return {
519
+ ...prev,
520
+ conversationId,
521
+ messages: mergedMessages,
522
+ };
523
+ });
524
+ }
449
525
  // Upload files if provided
450
526
  let fileIds;
451
527
  if (files && files.length > 0) {
452
528
  fileIds = [];
453
529
  for (const file of files) {
454
530
  try {
455
- const uploadedFile = await apiClient.current.uploadFile(state.conversationId, file);
531
+ const uploadedFile = await apiClient.current.uploadFile(conversationId, file);
456
532
  fileIds.push(uploadedFile.id);
457
533
  }
458
534
  catch (uploadError) {
@@ -466,11 +542,11 @@ function useChat(options) {
466
542
  let response;
467
543
  if (useAgent) {
468
544
  // Use agent endpoint - returns ConversationMessage[]
469
- response = await apiClient.current.sendAgentMessage(state.conversationId, content.trim(), fileIds);
545
+ response = await apiClient.current.sendAgentMessage(conversationId, trimmedContent, fileIds);
470
546
  }
471
547
  else {
472
548
  // Use standard chat endpoint
473
- response = await apiClient.current.sendMessage(state.conversationId, content.trim(), fileIds);
549
+ response = await apiClient.current.sendMessage(conversationId, trimmedContent, fileIds);
474
550
  }
475
551
  // Both endpoints now return ConversationMessage[] array (FULL conversation)
476
552
  if (Array.isArray(response)) {
@@ -582,12 +658,12 @@ function useChat(options) {
582
658
  setState(prev => ({
583
659
  ...prev,
584
660
  messages: [],
585
- conversationId: generateSessionId(),
661
+ conversationId: '',
586
662
  error: null,
587
663
  }));
588
664
  const persistConversation = state.config?.behavior.persistConversation ?? true;
589
665
  if (persistConversation && isStorageAvailable()) {
590
- saveConversation(widgetId, generateSessionId(), []);
666
+ clearConversation(widgetId);
591
667
  }
592
668
  }, [widgetId, state.config?.behavior.persistConversation]);
593
669
  /**