@chatwidgetai/chat-widget 0.1.0 → 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/index.js CHANGED
@@ -7,9 +7,81 @@ var react = require('react');
7
7
  * API Client for Widget Communication
8
8
  * Handles all HTTP requests to the widget API
9
9
  */
10
+ class ApiError extends Error {
11
+ constructor(message, status, options) {
12
+ super(message);
13
+ this.name = 'ApiError';
14
+ this.status = status;
15
+ this.details = options?.details;
16
+ this.retryAfterMs = options?.retryAfterMs;
17
+ }
18
+ }
19
+ function parseRetryAfter(headerValue) {
20
+ if (!headerValue)
21
+ return undefined;
22
+ const seconds = Number(headerValue);
23
+ if (!Number.isNaN(seconds) && seconds >= 0) {
24
+ return seconds * 1000;
25
+ }
26
+ const date = Date.parse(headerValue);
27
+ if (!Number.isNaN(date)) {
28
+ return Math.max(0, date - Date.now());
29
+ }
30
+ return undefined;
31
+ }
32
+ async function buildApiError(response, defaultMessage) {
33
+ let parsedBody = null;
34
+ let message = defaultMessage;
35
+ try {
36
+ const raw = await response.text();
37
+ if (raw) {
38
+ try {
39
+ parsedBody = JSON.parse(raw);
40
+ if (typeof parsedBody === 'object' && parsedBody !== null) {
41
+ const body = parsedBody;
42
+ message = typeof body.error === 'string'
43
+ ? body.error
44
+ : typeof body.message === 'string'
45
+ ? body.message
46
+ : defaultMessage;
47
+ }
48
+ else {
49
+ message = raw;
50
+ }
51
+ }
52
+ catch {
53
+ message = raw;
54
+ }
55
+ }
56
+ }
57
+ catch {
58
+ // ignore body parsing errors
59
+ }
60
+ let retryAfterMs = parseRetryAfter(response.headers.get('retry-after'));
61
+ if (retryAfterMs === undefined) {
62
+ retryAfterMs = parseRetryAfter(response.headers.get('x-ratelimit-reset'));
63
+ }
64
+ return new ApiError(message || defaultMessage, response.status, {
65
+ details: parsedBody,
66
+ retryAfterMs,
67
+ });
68
+ }
10
69
  class WidgetApiClient {
11
70
  constructor(config) {
12
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;
13
85
  }
14
86
  /**
15
87
  * Get widget configuration
@@ -23,23 +95,32 @@ class WidgetApiClient {
23
95
  },
24
96
  });
25
97
  if (!response.ok) {
26
- throw new Error(`Failed to fetch config: ${response.statusText}`);
98
+ throw await buildApiError(response, 'Failed to fetch config');
27
99
  }
28
100
  const json = await response.json();
29
101
  return json;
30
102
  }
31
103
  async getOrCreateConversation(conversationId) {
32
- const response = await fetch(`${this.config.apiUrl}/api/widget/${this.config.widgetId}/conversation/${conversationId || '0'}`, {
104
+ const baseUrl = `${this.config.apiUrl}/api/widget/${this.config.widgetId}/conversation`;
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()}` : '';
114
+ const response = await fetch(`${baseUrl}${query}`, {
33
115
  method: 'GET',
34
116
  headers: {
35
117
  'Authorization': `Bearer ${this.config.apiKey}`,
36
- }
118
+ },
37
119
  });
38
120
  if (!response.ok) {
39
- const error = await response.json().catch(() => ({ error: response.statusText }));
40
- throw new Error(error.error || 'Failed to upload file');
121
+ throw await buildApiError(response, 'Failed to load conversation');
41
122
  }
42
- return await response.json();
123
+ return response.json();
43
124
  }
44
125
  /**
45
126
  * Upload a file
@@ -56,8 +137,7 @@ class WidgetApiClient {
56
137
  body: formData,
57
138
  });
58
139
  if (!response.ok) {
59
- const error = await response.json().catch(() => ({ error: response.statusText }));
60
- throw new Error(error.error || 'Failed to upload file');
140
+ throw await buildApiError(response, 'Failed to upload file');
61
141
  }
62
142
  const result = await response.json();
63
143
  return result.file;
@@ -77,11 +157,11 @@ class WidgetApiClient {
77
157
  conversationId: conversationId,
78
158
  message,
79
159
  fileIds,
160
+ timeZone: this.getTimeZone(),
80
161
  }),
81
162
  });
82
163
  if (!response.ok) {
83
- const error = await response.json().catch(() => ({ error: response.statusText }));
84
- throw new Error(error.error || 'Failed to send message');
164
+ throw await buildApiError(response, 'Failed to send message');
85
165
  }
86
166
  return response.json();
87
167
  }
@@ -100,11 +180,11 @@ class WidgetApiClient {
100
180
  conversationId: conversationId,
101
181
  message,
102
182
  fileIds,
183
+ timeZone: this.getTimeZone(),
103
184
  }),
104
185
  });
105
186
  if (!response.ok) {
106
- const error = await response.json().catch(() => ({ error: response.statusText }));
107
- throw new Error(error.error || `Agent request failed with status ${response.status}`);
187
+ throw await buildApiError(response, `Agent request failed with status ${response.status}`);
108
188
  }
109
189
  const data = await response.json();
110
190
  // Check if response indicates an error
@@ -116,7 +196,7 @@ class WidgetApiClient {
116
196
  catch (error) {
117
197
  // Enhance error messages
118
198
  if (error instanceof TypeError && error.message.includes('fetch')) {
119
- throw new Error('Network error: Unable to reach the server');
199
+ throw new ApiError('Network error: Unable to reach the server', 0);
120
200
  }
121
201
  throw error;
122
202
  }
@@ -138,13 +218,13 @@ class WidgetApiClient {
138
218
  }),
139
219
  });
140
220
  if (!response.ok) {
141
- const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
221
+ const apiError = await buildApiError(response, 'Failed to submit feedback');
142
222
  console.error('Feedback submission failed:', {
143
223
  status: response.status,
144
- error: errorData,
224
+ error: apiError.details,
145
225
  payload: { session_id: sessionId, message_id: messageId, feedback }
146
226
  });
147
- throw new Error(errorData.error || 'Failed to submit feedback');
227
+ throw apiError;
148
228
  }
149
229
  }
150
230
  /**
@@ -177,11 +257,6 @@ class WidgetApiClient {
177
257
  /**
178
258
  * Generate a unique session ID
179
259
  */
180
- function generateSessionId() {
181
- const timestamp = Date.now().toString(36);
182
- const randomStr = Math.random().toString(36).substring(2, 15);
183
- return `session_${timestamp}_${randomStr}`;
184
- }
185
260
  /**
186
261
  * Generate a unique message ID
187
262
  */
@@ -236,6 +311,18 @@ function loadConversation(widgetId) {
236
311
  return null;
237
312
  }
238
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
+ }
239
326
  /**
240
327
  * Check if localStorage is available
241
328
  */
@@ -252,111 +339,70 @@ function isStorageAvailable() {
252
339
  }
253
340
 
254
341
  /**
255
- * Client Action Executor
256
- * Handles execution of client-side actions
257
- */
258
- /**
259
- * Execute a client-side action
342
+ * useChat Hook
343
+ * Main state management for chat functionality
260
344
  */
261
- async function executeClientAction(action) {
262
- try {
263
- console.log(`[ActionExecutor] Executing action: ${action.implementation}`, action.parameters);
264
- switch (action.implementation) {
265
- case 'redirect':
266
- return executeRedirect(action.parameters);
267
- case 'show_modal':
268
- return executeShowModal(action.parameters);
269
- case 'copy_to_clipboard':
270
- return executeCopyToClipboard(action.parameters);
271
- case 'open_chat':
272
- return executeOpenChat(action.parameters);
345
+ function deriveErrorInfo(error) {
346
+ if (error instanceof ApiError) {
347
+ const retryAfterSeconds = typeof error.retryAfterMs === 'number'
348
+ ? Math.max(1, Math.ceil(error.retryAfterMs / 1000))
349
+ : undefined;
350
+ const lowerMessage = (error.message || '').toLowerCase();
351
+ let message;
352
+ switch (error.status) {
353
+ case 429: {
354
+ const isPerUser = lowerMessage.includes('user');
355
+ const base = isPerUser
356
+ ? 'You have reached the per-user rate limit.'
357
+ : 'This widget has received too many requests.';
358
+ if (retryAfterSeconds) {
359
+ message = `${base} Please wait ${retryAfterSeconds} second${retryAfterSeconds === 1 ? '' : 's'} before trying again.`;
360
+ }
361
+ else {
362
+ message = `${base} Please wait a moment and try again.`;
363
+ }
364
+ break;
365
+ }
366
+ case 401:
367
+ message = 'Authentication failed. Please refresh the page or verify your API key.';
368
+ break;
369
+ case 403:
370
+ message = 'Access to this widget is restricted. Please contact the site owner if you believe this is an error.';
371
+ break;
372
+ case 404:
373
+ message = 'We could not find this widget. It may have been removed.';
374
+ break;
273
375
  default:
274
- console.warn(`[ActionExecutor] Unknown action: ${action.implementation}`);
275
- return {
276
- success: false,
277
- error: `Unknown action type: ${action.implementation}`
278
- };
376
+ if (error.status >= 500) {
377
+ message = 'The server encountered an error. Please try again shortly.';
378
+ }
379
+ else if (error.status > 0) {
380
+ message = error.message || 'Something went wrong. Please try again.';
381
+ }
382
+ else {
383
+ message = error.message || 'Unable to connect to the server. Please check your internet connection.';
384
+ }
279
385
  }
386
+ return { message, retryAfterSeconds, status: error.status };
280
387
  }
281
- catch (error) {
282
- console.error(`[ActionExecutor] Error executing action:`, error);
283
- return {
284
- success: false,
285
- error: error instanceof Error ? error.message : 'Unknown error'
286
- };
287
- }
288
- }
289
- /**
290
- * Redirect to a URL
291
- */
292
- function executeRedirect(params) {
293
- const url = params.url;
294
- if (!url) {
295
- return { success: false, error: 'URL parameter is required' };
296
- }
297
- try {
298
- // Validate URL
299
- new URL(url);
300
- // Redirect
301
- window.location.href = url;
302
- return { success: true, result: { redirected_to: url } };
303
- }
304
- catch (error) {
305
- return { success: false, error: 'Invalid URL' };
306
- }
307
- }
308
- /**
309
- * Show a modal
310
- */
311
- function executeShowModal(params) {
312
- const { title, content } = params;
313
- if (!title || !content) {
314
- return { success: false, error: 'Title and content are required' };
315
- }
316
- // Use browser's alert as a simple modal
317
- // In a real implementation, you'd use a proper modal component
318
- alert(`${title}\n\n${content}`);
319
- return { success: true, result: { shown: true } };
320
- }
321
- /**
322
- * Copy text to clipboard
323
- */
324
- async function executeCopyToClipboard(params) {
325
- const text = params.text;
326
- if (!text) {
327
- return { success: false, error: 'Text parameter is required' };
328
- }
329
- try {
330
- await navigator.clipboard.writeText(text);
331
- return { success: true, result: { copied: text } };
332
- }
333
- catch (error) {
334
- return {
335
- success: false,
336
- error: 'Failed to copy to clipboard. Please check browser permissions.'
337
- };
338
- }
339
- }
340
- /**
341
- * Open chat with a specific message
342
- */
343
- function executeOpenChat(params) {
344
- const message = params.message;
345
- if (!message) {
346
- return { success: false, error: 'Message parameter is required' };
388
+ if (error instanceof Error) {
389
+ const lower = error.message.toLowerCase();
390
+ if (lower.includes('network')) {
391
+ return { message: 'Unable to connect to the server. Please check your internet connection.' };
392
+ }
393
+ if (lower.includes('timeout')) {
394
+ return { message: 'The request timed out. Please try again.' };
395
+ }
396
+ if (lower.includes('unauthorized') || lower.includes('401')) {
397
+ return { message: 'Authentication failed. Please refresh the page or verify your API key.' };
398
+ }
399
+ if (lower.includes('internal server error') || lower.includes('500')) {
400
+ return { message: 'The server encountered an error. Please try again shortly.' };
401
+ }
402
+ return { message: error.message || 'Something went wrong. Please try again.' };
347
403
  }
348
- // Dispatch a custom event that the chat widget can listen to
349
- const event = new CustomEvent('widget:open-chat', {
350
- detail: { message }
351
- });
352
- window.dispatchEvent(event);
353
- return { success: true, result: { message } };
404
+ return { message: 'Something went wrong. Please try again.' };
354
405
  }
355
-
356
- /**
357
- * useChat Hook
358
- * Main state management for chat functionality
359
- */
360
406
  function useChat(options) {
361
407
  const { widgetId, apiKey, apiUrl, onMessage, onError, } = options;
362
408
  const [state, setState] = react.useState({
@@ -368,42 +414,62 @@ function useChat(options) {
368
414
  conversationId: '', // Will be set after loading conversation
369
415
  config: null,
370
416
  });
371
- const [pendingAction, setPendingAction] = react.useState(null);
372
417
  const apiClient = react.useRef(new WidgetApiClient({ widgetId, apiKey, apiUrl }));
373
- // Load configuration and conversation on mount
418
+ // Load configuration on mount and hydrate with existing conversation if available
374
419
  react.useEffect(() => {
420
+ let isMounted = true;
375
421
  const initialize = async () => {
376
422
  try {
377
- // Load config
378
423
  const config = await apiClient.current.getConfig();
379
- // Get or create conversation
380
424
  const persistConversation = config.behavior.persistConversation ?? true;
381
- let conversationId;
425
+ let conversationId = '';
426
+ let messages = [];
382
427
  if (persistConversation && isStorageAvailable()) {
383
428
  const stored = loadConversation(widgetId);
384
- conversationId = stored?.conversationId || stored?.sessionId; // Support old sessionId for backwards compatibility
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;
385
443
  }
386
- // Get conversation from backend
387
- const conversation = await apiClient.current.getOrCreateConversation(conversationId);
388
444
  setState(prev => ({
389
445
  ...prev,
390
446
  config,
391
- conversationId: conversation.id,
392
- messages: conversation.messages,
447
+ conversationId,
448
+ messages,
393
449
  }));
394
450
  }
395
451
  catch (error) {
396
- const err = error instanceof Error ? error : new Error('Failed to initialize');
397
- setState(prev => ({ ...prev, error: err.message }));
452
+ if (!isMounted) {
453
+ return;
454
+ }
455
+ const errorInfo = deriveErrorInfo(error);
456
+ const err = error instanceof Error ? error : new Error(errorInfo.message);
457
+ setState(prev => ({ ...prev, error: errorInfo.message }));
398
458
  onError?.(err);
399
459
  }
400
460
  };
401
461
  initialize();
462
+ return () => {
463
+ isMounted = false;
464
+ };
402
465
  }, [widgetId, apiKey, apiUrl, onError]);
403
466
  // Save conversation when messages change
404
467
  react.useEffect(() => {
405
468
  const persistConversation = state.config?.behavior.persistConversation ?? true;
406
- if (persistConversation && isStorageAvailable() && state.messages.length > 0) {
469
+ if (persistConversation &&
470
+ isStorageAvailable() &&
471
+ state.messages.length > 0 &&
472
+ state.conversationId) {
407
473
  saveConversation(widgetId, state.conversationId, state.messages);
408
474
  }
409
475
  }, [widgetId, state.messages, state.conversationId, state.config?.behavior.persistConversation]);
@@ -411,13 +477,15 @@ function useChat(options) {
411
477
  * Send a message
412
478
  */
413
479
  const sendMessage = react.useCallback(async (content, files) => {
414
- if (!content.trim() && (!files || files.length === 0))
480
+ const trimmedContent = content.trim();
481
+ const hasFiles = !!files && files.length > 0;
482
+ if (!trimmedContent && !hasFiles)
415
483
  return;
416
484
  const userMessage = {
417
485
  id: generateMessageId(),
418
486
  message: {
419
487
  type: 'human',
420
- content: content.trim(),
488
+ content: trimmedContent,
421
489
  },
422
490
  timestamp: new Date().toISOString(),
423
491
  sources: [],
@@ -432,13 +500,37 @@ function useChat(options) {
432
500
  }));
433
501
  onMessage?.(userMessage);
434
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
+ }
435
527
  // Upload files if provided
436
528
  let fileIds;
437
529
  if (files && files.length > 0) {
438
530
  fileIds = [];
439
531
  for (const file of files) {
440
532
  try {
441
- const uploadedFile = await apiClient.current.uploadFile(state.conversationId, file);
533
+ const uploadedFile = await apiClient.current.uploadFile(conversationId, file);
442
534
  fileIds.push(uploadedFile.id);
443
535
  }
444
536
  catch (uploadError) {
@@ -449,15 +541,14 @@ function useChat(options) {
449
541
  }
450
542
  // Determine if widget has actions (use agent endpoint)
451
543
  const useAgent = state.config?.behavior.agentic || (state.config?.actions && state.config.actions.length > 0);
452
- console.log(useAgent);
453
544
  let response;
454
545
  if (useAgent) {
455
546
  // Use agent endpoint - returns ConversationMessage[]
456
- response = await apiClient.current.sendAgentMessage(state.conversationId, content.trim(), fileIds);
547
+ response = await apiClient.current.sendAgentMessage(conversationId, trimmedContent, fileIds);
457
548
  }
458
549
  else {
459
550
  // Use standard chat endpoint
460
- response = await apiClient.current.sendMessage(state.conversationId, content.trim(), fileIds);
551
+ response = await apiClient.current.sendMessage(conversationId, trimmedContent, fileIds);
461
552
  }
462
553
  // Both endpoints now return ConversationMessage[] array (FULL conversation)
463
554
  if (Array.isArray(response)) {
@@ -518,26 +609,9 @@ function useChat(options) {
518
609
  }
519
610
  }
520
611
  catch (error) {
521
- const err = error instanceof Error ? error : new Error('Failed to send message');
522
- // Determine user-friendly error message
523
- let userMessage = err.message;
524
- // Handle specific error types
525
- if (err.message.includes('Network') || err.message.includes('fetch')) {
526
- userMessage = 'Unable to connect to the server. Please check your internet connection.';
527
- }
528
- else if (err.message.includes('401') || err.message.includes('Unauthorized')) {
529
- userMessage = 'Authentication failed. Please refresh the page.';
530
- }
531
- else if (err.message.includes('500') || err.message.includes('Internal Server Error')) {
532
- userMessage = 'The server encountered an error. Please try again later.';
533
- }
534
- else if (err.message.includes('timeout')) {
535
- userMessage = 'Request timed out. Please try again.';
536
- }
537
- // Use fallback message if configured, otherwise use error message
612
+ const errorInfo = deriveErrorInfo(error);
613
+ const err = error instanceof Error ? error : new Error(errorInfo.message);
538
614
  const fallbackMessage = state.config?.behavior?.fallbackMessage;
539
- const errorMessage = fallbackMessage || userMessage;
540
- // If fallback message is configured, add it as an assistant message
541
615
  if (fallbackMessage) {
542
616
  const fallbackAssistantMessage = {
543
617
  id: generateMessageId(),
@@ -553,17 +627,16 @@ function useChat(options) {
553
627
  messages: [...prev.messages, fallbackAssistantMessage],
554
628
  isLoading: false,
555
629
  isTyping: false,
556
- error: null,
630
+ error: errorInfo.message,
557
631
  }));
558
632
  onMessage?.(fallbackAssistantMessage);
559
633
  }
560
634
  else {
561
- // Show error message as assistant message for better UX
562
635
  const errorAssistantMessage = {
563
636
  id: generateMessageId(),
564
637
  message: {
565
638
  type: 'ai',
566
- content: `⚠️ ${errorMessage}`,
639
+ content: `⚠️ ${errorInfo.message}`,
567
640
  },
568
641
  timestamp: new Date().toISOString(),
569
642
  sources: [],
@@ -573,7 +646,7 @@ function useChat(options) {
573
646
  messages: [...prev.messages, errorAssistantMessage],
574
647
  isLoading: false,
575
648
  isTyping: false,
576
- error: errorMessage,
649
+ error: errorInfo.message,
577
650
  }));
578
651
  onMessage?.(errorAssistantMessage);
579
652
  }
@@ -587,12 +660,12 @@ function useChat(options) {
587
660
  setState(prev => ({
588
661
  ...prev,
589
662
  messages: [],
590
- conversationId: generateSessionId(),
663
+ conversationId: '',
591
664
  error: null,
592
665
  }));
593
666
  const persistConversation = state.config?.behavior.persistConversation ?? true;
594
667
  if (persistConversation && isStorageAvailable()) {
595
- saveConversation(widgetId, generateSessionId(), []);
668
+ clearConversation(widgetId);
596
669
  }
597
670
  }, [widgetId, state.config?.behavior.persistConversation]);
598
671
  /**
@@ -611,81 +684,12 @@ function useChat(options) {
611
684
  }));
612
685
  }
613
686
  catch (error) {
614
- const err = error instanceof Error ? error : new Error('Failed to submit feedback');
687
+ const errorInfo = deriveErrorInfo(error);
688
+ const err = error instanceof Error ? error : new Error(errorInfo.message);
689
+ setState(prev => ({ ...prev, error: errorInfo.message }));
615
690
  onError?.(err);
616
691
  }
617
692
  }, [state.conversationId, onError]);
618
- /**
619
- * Approve and execute pending action
620
- */
621
- const approveAction = react.useCallback(async () => {
622
- if (!pendingAction)
623
- return;
624
- setState(prev => ({ ...prev, isLoading: true, isTyping: true }));
625
- try {
626
- // Execute the client action
627
- const actionResult = await executeClientAction(pendingAction.action);
628
- // Update the pending action message to show execution result
629
- const executionMessage = actionResult.success
630
- ? `✓ Action executed successfully`
631
- : `✗ Action failed: ${actionResult.error}`;
632
- setState(prev => ({
633
- ...prev,
634
- messages: prev.messages.map(msg => msg.id === pendingAction.messageId
635
- ? { ...msg, message: { ...msg.message, content: `${msg.message.content}\n\n${executionMessage}` } }
636
- : msg),
637
- }));
638
- // Clear pending action
639
- setPendingAction(null);
640
- // TODO: Implement continueAgent in API client if actions are needed
641
- // For now, just show success message
642
- const assistantMessage = {
643
- id: generateMessageId(),
644
- message: {
645
- type: 'ai',
646
- content: 'Action completed successfully.',
647
- },
648
- timestamp: new Date().toISOString(),
649
- sources: [],
650
- };
651
- setState(prev => ({
652
- ...prev,
653
- messages: [...prev.messages, assistantMessage],
654
- isLoading: false,
655
- isTyping: false,
656
- }));
657
- onMessage?.(assistantMessage);
658
- }
659
- catch (error) {
660
- const err = error instanceof Error ? error : new Error('Failed to execute action');
661
- setState(prev => ({
662
- ...prev,
663
- isLoading: false,
664
- isTyping: false,
665
- error: err.message,
666
- messages: prev.messages.map(msg => msg.id === pendingAction.messageId
667
- ? { ...msg, message: { ...msg.message, content: `${msg.message.content}\n\n✗ Failed to execute action` } }
668
- : msg),
669
- }));
670
- setPendingAction(null);
671
- onError?.(err);
672
- }
673
- }, [pendingAction, state.conversationId, onMessage, onError]);
674
- /**
675
- * Reject pending action
676
- */
677
- const rejectAction = react.useCallback(() => {
678
- if (!pendingAction)
679
- return;
680
- // Update message to show rejection
681
- setState(prev => ({
682
- ...prev,
683
- messages: prev.messages.map(msg => msg.id === pendingAction.messageId
684
- ? { ...msg, message: { ...msg.message, content: `${msg.message.content}\n\n✗ Action cancelled by user` } }
685
- : msg),
686
- }));
687
- setPendingAction(null);
688
- }, [pendingAction]);
689
693
  return {
690
694
  messages: state.messages,
691
695
  isLoading: state.isLoading,
@@ -693,10 +697,7 @@ function useChat(options) {
693
697
  error: state.error,
694
698
  config: state.config,
695
699
  conversationId: state.conversationId,
696
- pendingAction,
697
700
  sendMessage,
698
- approveAction,
699
- rejectAction,
700
701
  clearMessages,
701
702
  submitFeedback,
702
703
  };
@@ -21611,7 +21612,7 @@ styleInject(css_248z);
21611
21612
 
21612
21613
  const ChatWidget = ({ widgetId, apiKey, apiUrl = window.location.origin, position = 'bottom-right', theme: themeOverride, primaryColor, onOpen, onClose, onMessage, onError, }) => {
21613
21614
  const [isOpen, setIsOpen] = react.useState(false);
21614
- const { messages, isLoading, isTyping, error, config, sendMessage, approveAction, rejectAction, submitFeedback, } = useChat({
21615
+ const { messages, isLoading, isTyping, error, config, sendMessage, submitFeedback, } = useChat({
21615
21616
  widgetId,
21616
21617
  apiKey,
21617
21618
  apiUrl,
@@ -21661,12 +21662,13 @@ const ChatWidget = ({ widgetId, apiKey, apiUrl = window.location.origin, positio
21661
21662
  const handleFeedback = async (messageId, feedback) => {
21662
21663
  await submitFeedback(messageId, feedback);
21663
21664
  };
21664
- return (jsxRuntime.jsx("div", { className: `ai-chat-widget ${effectiveTheme}`, style: customStyles, children: jsxRuntime.jsx("div", { className: `ai-chat-widget-container ${effectivePosition}`, children: isOpen ? (jsxRuntime.jsx(ChatWindow, { messages: messages, isLoading: isLoading, isTyping: isTyping, error: error, config: config, onSendMessage: sendMessage, onApproveAction: approveAction, onRejectAction: rejectAction, onClose: handleToggle, onFeedback: handleFeedback })) : (jsxRuntime.jsx("button", { className: "ai-chat-button", onClick: handleToggle, "aria-label": "Open chat", style: {
21665
+ return (jsxRuntime.jsx("div", { className: `ai-chat-widget ${effectiveTheme}`, style: customStyles, children: jsxRuntime.jsx("div", { className: `ai-chat-widget-container ${effectivePosition}`, children: isOpen ? (jsxRuntime.jsx(ChatWindow, { messages: messages, isLoading: isLoading, isTyping: isTyping, error: error, config: config, onSendMessage: sendMessage, onClose: handleToggle, onFeedback: handleFeedback })) : (jsxRuntime.jsx("button", { className: "ai-chat-button", onClick: handleToggle, "aria-label": "Open chat", style: {
21665
21666
  width: config?.appearance.buttonSize || 60,
21666
21667
  height: config?.appearance.buttonSize || 60,
21667
21668
  }, children: config?.appearance.buttonIcon ? (jsxRuntime.jsx("span", { className: "ai-chat-button-icon", children: config.appearance.buttonIcon })) : (jsxRuntime.jsx("svg", { className: "ai-chat-button-svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) })) })) }) }));
21668
21669
  };
21669
21670
 
21671
+ exports.ApiError = ApiError;
21670
21672
  exports.ChatWidget = ChatWidget;
21671
21673
  exports.useChat = useChat;
21672
21674
  //# sourceMappingURL=index.js.map