@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
|
-
|
|
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
|
-
|
|
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);
|