@aslaluroba/help-center 4.0.9 → 4.0.10

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.
@@ -2491,7 +2491,7 @@ class ClientAblyService {
2491
2491
  static sessionId = null;
2492
2492
  static messageUnsubscribe = null;
2493
2493
  static connectionTimeout;
2494
- static async startConnection(sessionId, ablyToken, onMessageReceived, tenantId, getAblyToken) {
2494
+ static async startConnection(sessionId, ablyToken, onMessageReceived, tenantId, getAblyToken, onActionReceived) {
2495
2495
  // Prevent multiple connections
2496
2496
  if (this.isConnected && this.sessionId === sessionId) {
2497
2497
  return;
@@ -2573,7 +2573,7 @@ class ClientAblyService {
2573
2573
  }, 10000);
2574
2574
  });
2575
2575
  // Subscribe to the session room
2576
- await this.joinChannel(sessionId, onMessageReceived, tenantId);
2576
+ await this.joinChannel(sessionId, onMessageReceived, tenantId, onActionReceived);
2577
2577
  }
2578
2578
  catch (error) {
2579
2579
  console.error('Error during Ably connection setup:', error);
@@ -2600,7 +2600,7 @@ class ClientAblyService {
2600
2600
  throw error;
2601
2601
  }
2602
2602
  }
2603
- static async joinChannel(sessionId, onMessageReceived, tenantId) {
2603
+ static async joinChannel(sessionId, onMessageReceived, tenantId, onActionReceived) {
2604
2604
  if (!this.client) {
2605
2605
  throw new Error('Chat client not initialized');
2606
2606
  }
@@ -2618,10 +2618,21 @@ class ClientAblyService {
2618
2618
  ? messageData
2619
2619
  : messageData?.content || messageData?.message || '';
2620
2620
  const senderType = messageData?.senderType || 3; // Assistant
2621
+ const attachments = messageData?.attachments || messageData?.attachmentIds;
2622
+ const actionType = messageData?.actionType;
2623
+ // Handle action type using extensible action handler
2624
+ // This supports all action types (known and unknown) in a safe, extensible way
2625
+ if (onActionReceived && actionType !== undefined && actionType !== null) {
2626
+ // Execute action handler (may be async, but we don't await to avoid blocking message processing)
2627
+ Promise.resolve(onActionReceived(actionType, messageData)).catch((error) => {
2628
+ console.error('Error handling action type:', actionType, error);
2629
+ });
2630
+ }
2631
+ // Extract needsAgent flag (for backward compatibility)
2632
+ // This is separate from action handling to maintain existing behavior
2621
2633
  const needsAgent = messageData?.needsAgent ||
2622
2634
  messageData?.actionType == 'needs_agent' ||
2623
2635
  false;
2624
- const attachments = messageData?.attachments || messageData?.attachmentIds;
2625
2636
  // Pass message as object if it has attachments, otherwise as string for backward compatibility
2626
2637
  const messageToPass = attachments?.length > 0
2627
2638
  ? { content: messageContent, attachments }
@@ -2700,6 +2711,112 @@ class ClientAblyService {
2700
2711
  }
2701
2712
  }
2702
2713
 
2714
+ /**
2715
+ * Service for managing AI response action handlers in an extensible way.
2716
+ *
2717
+ * This service provides a registry pattern for handling different action types
2718
+ * from realtime AI responses, making it easy to add new actions without
2719
+ * modifying existing code.
2720
+ *
2721
+ * @publicApi
2722
+ */
2723
+ class ActionHandlerService {
2724
+ actionHandlers = new Map();
2725
+ defaultHandler;
2726
+ constructor() {
2727
+ // Register default handler for unknown actions
2728
+ this.setDefaultHandler((messageData) => {
2729
+ const actionType = messageData?.actionType || '';
2730
+ if (actionType && actionType !== '') {
2731
+ console.warn(`[ActionHandlerService] Unknown action type received: "${actionType}". ` +
2732
+ `Message data:`, messageData);
2733
+ }
2734
+ });
2735
+ }
2736
+ /**
2737
+ * Registers a handler for a specific action type
2738
+ *
2739
+ * @param actionType The action type string (e.g., 'needs_agent', 'end_session')
2740
+ * @param handler The handler function to execute when this action is received
2741
+ */
2742
+ registerHandler(actionType, handler) {
2743
+ if (this.actionHandlers.has(actionType)) {
2744
+ console.warn(`[ActionHandlerService] Handler for action type "${actionType}" already exists. ` +
2745
+ `Overwriting with new handler.`);
2746
+ }
2747
+ this.actionHandlers.set(actionType, handler);
2748
+ }
2749
+ /**
2750
+ * Unregisters a handler for a specific action type
2751
+ *
2752
+ * @param actionType The action type to unregister
2753
+ */
2754
+ unregisterHandler(actionType) {
2755
+ this.actionHandlers.delete(actionType);
2756
+ }
2757
+ /**
2758
+ * Sets the default handler for unknown action types
2759
+ *
2760
+ * @param handler The default handler function
2761
+ */
2762
+ setDefaultHandler(handler) {
2763
+ this.defaultHandler = handler;
2764
+ }
2765
+ /**
2766
+ * Handles an action type by executing the registered handler or default handler
2767
+ *
2768
+ * @param actionType The action type to handle
2769
+ * @param messageData The message data containing the action
2770
+ * @returns Promise that resolves when handling is complete
2771
+ */
2772
+ async handleAction(actionType, messageData) {
2773
+ // Handle empty string or null/undefined - do nothing as per requirements
2774
+ if (!actionType || actionType === '') {
2775
+ return;
2776
+ }
2777
+ const handler = this.actionHandlers.get(actionType);
2778
+ if (handler) {
2779
+ try {
2780
+ await handler(messageData);
2781
+ }
2782
+ catch (error) {
2783
+ console.error(`[ActionHandlerService] Error executing handler for action type "${actionType}":`, error);
2784
+ }
2785
+ }
2786
+ else if (this.defaultHandler) {
2787
+ // Use default handler for unknown actions
2788
+ try {
2789
+ await this.defaultHandler(messageData);
2790
+ }
2791
+ catch (error) {
2792
+ console.error(`[ActionHandlerService] Error executing default handler for action type "${actionType}":`, error);
2793
+ }
2794
+ }
2795
+ }
2796
+ /**
2797
+ * Gets all registered action types
2798
+ *
2799
+ * @returns Array of registered action type strings
2800
+ */
2801
+ getRegisteredActions() {
2802
+ return Array.from(this.actionHandlers.keys());
2803
+ }
2804
+ /**
2805
+ * Clears all registered handlers (useful for testing or cleanup)
2806
+ */
2807
+ clearHandlers() {
2808
+ this.actionHandlers.clear();
2809
+ }
2810
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: ActionHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2811
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: ActionHandlerService, providedIn: 'root' });
2812
+ }
2813
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: ActionHandlerService, decorators: [{
2814
+ type: Injectable,
2815
+ args: [{
2816
+ providedIn: 'root'
2817
+ }]
2818
+ }], ctorParameters: () => [] });
2819
+
2703
2820
  /**
2704
2821
  * Service for managing chat session lifecycle and operations.
2705
2822
  *
@@ -2712,6 +2829,7 @@ class ClientAblyService {
2712
2829
  */
2713
2830
  class ChatSessionService {
2714
2831
  apiService = inject(ApiService);
2832
+ actionHandlerService = inject(ActionHandlerService);
2715
2833
  /**
2716
2834
  * Creates a new chat session with the specified option.
2717
2835
  *
@@ -2741,8 +2859,9 @@ class ChatSessionService {
2741
2859
  * @param option Optional option for token refresh (needed for token renewal)
2742
2860
  * @param helpScreenId Optional help screen ID for token refresh
2743
2861
  * @param currentLang Optional current language for token refresh
2862
+ * @param onActionReceived Optional callback function for handling action types
2744
2863
  */
2745
- async establishAblyConnection(sessionId, ablyToken, onMessageReceived, tenantId, option, helpScreenId, currentLang) {
2864
+ async establishAblyConnection(sessionId, ablyToken, onMessageReceived, tenantId, option, helpScreenId, currentLang, onActionReceived) {
2746
2865
  // Create token refresh callback if context is provided
2747
2866
  let getAblyToken;
2748
2867
  if (option && helpScreenId && currentLang) {
@@ -2762,7 +2881,11 @@ class ChatSessionService {
2762
2881
  }
2763
2882
  };
2764
2883
  }
2765
- await ClientAblyService.startConnection(sessionId, ablyToken, onMessageReceived, tenantId, getAblyToken);
2884
+ // Create action handler callback that uses the ActionHandlerService
2885
+ const actionHandler = onActionReceived || ((actionType, messageData) => {
2886
+ this.actionHandlerService.handleAction(actionType, messageData);
2887
+ });
2888
+ await ClientAblyService.startConnection(sessionId, ablyToken, onMessageReceived, tenantId, getAblyToken, actionHandler);
2766
2889
  }
2767
2890
  /**
2768
2891
  * Sends a message in an active chat session.
@@ -2878,10 +3001,12 @@ class HelpCenterWidgetComponent {
2878
3001
  pendingNewChatOption = signal(null, ...(ngDevMode ? [{ debugName: "pendingNewChatOption" }] : []));
2879
3002
  hasUserSentMessages = signal(false, ...(ngDevMode ? [{ debugName: "hasUserSentMessages" }] : []));
2880
3003
  closedSessionIdForReview = signal(null, ...(ngDevMode ? [{ debugName: "closedSessionIdForReview" }] : []));
3004
+ waitingForSessionEndConfirmation = signal(false, ...(ngDevMode ? [{ debugName: "waitingForSessionEndConfirmation" }] : []));
2881
3005
  apiService = inject(ApiService);
2882
3006
  translationService = inject(TranslationService);
2883
3007
  themeService = inject(ThemeService);
2884
3008
  chatSessionService = inject(ChatSessionService);
3009
+ actionHandlerService = inject(ActionHandlerService);
2885
3010
  fileUploadService = inject(FileUploadService);
2886
3011
  destroyRef = inject(DestroyRef);
2887
3012
  themeEffectRef;
@@ -2914,6 +3039,24 @@ class HelpCenterWidgetComponent {
2914
3039
  // as it's controlled by the parent component
2915
3040
  }
2916
3041
  });
3042
+ // Register action handlers for extensible action handling
3043
+ this.registerActionHandlers();
3044
+ }
3045
+ /**
3046
+ * Registers action handlers for AI response actions.
3047
+ * This uses the ActionHandlerService for extensible, maintainable action handling.
3048
+ */
3049
+ registerActionHandlers() {
3050
+ // Register handler for 'needs_agent' action
3051
+ this.actionHandlerService.registerHandler('needs_agent', () => {
3052
+ // Trigger "handoff / needs agent" UI flow
3053
+ this.needsAgent.set(true);
3054
+ });
3055
+ // Register handler for 'end_session' action
3056
+ this.actionHandlerService.registerHandler('end_session', () => {
3057
+ // Trigger end session flow
3058
+ this.handleEndSessionReceived();
3059
+ });
2917
3060
  }
2918
3061
  ngOnInit() {
2919
3062
  this.showArrowAnimation.set(this.showArrow());
@@ -2985,7 +3128,11 @@ class HelpCenterWidgetComponent {
2985
3128
  // Read sessionId fresh after async operation to ensure we have the current value
2986
3129
  const currentSessionId = this.sessionId();
2987
3130
  if (currentSessionId) {
2988
- await this.chatSessionService.establishAblyConnection(currentSessionId, tokenToUse, this.handleReceiveMessage.bind(this), tenantId, selectedOpt, this.helpScreenId(), this.currentLang());
3131
+ // Use action handler service for extensible action handling
3132
+ const actionHandler = async (actionType, messageData) => {
3133
+ await this.actionHandlerService.handleAction(actionType, messageData);
3134
+ };
3135
+ await this.chatSessionService.establishAblyConnection(currentSessionId, tokenToUse, this.handleReceiveMessage.bind(this), tenantId, selectedOpt, this.helpScreenId(), this.currentLang(), actionHandler);
2989
3136
  this.isAblyConnected.set(true);
2990
3137
  }
2991
3138
  }
@@ -3103,6 +3250,13 @@ class HelpCenterWidgetComponent {
3103
3250
  const sender = this.getSenderType(senderType);
3104
3251
  const messageContent = typeof message === 'string' ? message : message.content || '';
3105
3252
  const attachmentIds = typeof message === 'string' ? undefined : message.attachments;
3253
+ // Check if we're waiting for session end confirmation
3254
+ // The next message after end_session is the confirmation
3255
+ if (this.waitingForSessionEndConfirmation()) {
3256
+ // This is the confirmation message, proceed with end chat flow
3257
+ this.waitingForSessionEndConfirmation.set(false);
3258
+ this.handleEndSessionConfirmed();
3259
+ }
3106
3260
  this.messages.update((msgs) => [
3107
3261
  ...msgs,
3108
3262
  {
@@ -3302,6 +3456,8 @@ class HelpCenterWidgetComponent {
3302
3456
  this.hasUserSentMessages.set(false);
3303
3457
  }
3304
3458
  async endChatSession() {
3459
+ // Reset waiting flag if connection closes before confirmation
3460
+ this.waitingForSessionEndConfirmation.set(false);
3305
3461
  // Read sessionId fresh to ensure we have the current value before async operations
3306
3462
  const currentSessionId = this.sessionId();
3307
3463
  if (currentSessionId) {
@@ -3324,6 +3480,8 @@ class HelpCenterWidgetComponent {
3324
3480
  this.hasUserSentMessages.set(false);
3325
3481
  }
3326
3482
  async closeSessionOnly() {
3483
+ // Reset waiting flag when closing session
3484
+ this.waitingForSessionEndConfirmation.set(false);
3327
3485
  // Read sessionId fresh to ensure we have the current value before async operations
3328
3486
  const currentSessionId = this.sessionId();
3329
3487
  if (currentSessionId) {
@@ -3389,6 +3547,8 @@ class HelpCenterWidgetComponent {
3389
3547
  this.pendingNewChatOption.set(null);
3390
3548
  }
3391
3549
  async clearCurrentChat() {
3550
+ // Reset waiting flag when clearing chat
3551
+ this.waitingForSessionEndConfirmation.set(false);
3392
3552
  // Read sessionId fresh to ensure we have the current value before async operations
3393
3553
  const currentSessionId = this.sessionId();
3394
3554
  if (currentSessionId) {
@@ -3555,6 +3715,27 @@ class HelpCenterWidgetComponent {
3555
3715
  },
3556
3716
  ]);
3557
3717
  }
3718
+ handleEndSessionReceived() {
3719
+ // Set flag to wait for confirmation message
3720
+ // The next message received will be treated as confirmation
3721
+ this.waitingForSessionEndConfirmation.set(true);
3722
+ }
3723
+ async handleEndSessionConfirmed() {
3724
+ // Only proceed if user has sent messages (to show review)
3725
+ if (!this.hasUserSentMessages()) {
3726
+ // No messages sent, just end the chat directly
3727
+ await this.endChatSession();
3728
+ return;
3729
+ }
3730
+ // Store session ID before closing
3731
+ const currentSessionId = this.sessionId();
3732
+ // Close the chat session on backend only (don't change UI state)
3733
+ await this.closeSessionOnly();
3734
+ // Store the closed session ID for review submission
3735
+ this.closedSessionIdForReview.set(currentSessionId);
3736
+ // Add review message to chat
3737
+ this.addReviewMessageToChat();
3738
+ }
3558
3739
  async handleReviewSubmitFromChat(reviewData) {
3559
3740
  try {
3560
3741
  this.isSubmittingReview.set(true);