@aslaluroba/help-center-react 3.2.4 → 3.2.5
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/core/AblyService.d.ts +4 -1
- package/dist/index.esm.js +467 -489
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +466 -488
- package/dist/index.js.map +1 -1
- package/dist/lib/custom-hooks/useActionHandler.d.ts +8 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/services.esm.js +349 -456
- package/dist/services.esm.js.map +1 -1
- package/dist/services.js +349 -456
- package/dist/services.js.map +1 -1
- package/package.json +1 -1
- package/src/core/AblyService.ts +38 -6
- package/src/lib/custom-hooks/useActionHandler.ts +102 -0
- package/src/lib/index.ts +1 -0
- package/src/ui/help-center.tsx +29 -4
- package/src/.DS_Store +0 -0
package/package.json
CHANGED
package/src/core/AblyService.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import * as Ably from 'ably';
|
|
2
2
|
|
|
3
|
+
type ActionHandlerCallback = (actionType: string | undefined | null, messageData: any) => void | Promise<void>;
|
|
4
|
+
|
|
3
5
|
export class ClientAblyService {
|
|
4
6
|
private static client: Ably.Realtime | null = null;
|
|
5
7
|
private static channel: Ably.RealtimeChannel | null = null;
|
|
6
8
|
private static isConnected: boolean = false;
|
|
7
9
|
private static sessionId: string | null = null;
|
|
8
10
|
private static messageUnsubscribe: (() => void) | null = null;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
private static onActionReceived: ActionHandlerCallback | null = null;
|
|
12
|
+
|
|
13
|
+
static async startConnection(
|
|
14
|
+
sessionId: string,
|
|
15
|
+
ablyToken: string,
|
|
16
|
+
onMessageReceived: Function,
|
|
17
|
+
tenantId: string,
|
|
18
|
+
onActionReceived?: ActionHandlerCallback
|
|
19
|
+
) {
|
|
11
20
|
// Prevent multiple connections
|
|
12
21
|
if (this.isConnected && this.sessionId === sessionId) {
|
|
13
22
|
return;
|
|
@@ -73,6 +82,9 @@ export class ClientAblyService {
|
|
|
73
82
|
}, 10000);
|
|
74
83
|
});
|
|
75
84
|
|
|
85
|
+
// Store optional action handler for this connection
|
|
86
|
+
this.onActionReceived = onActionReceived ?? null;
|
|
87
|
+
|
|
76
88
|
// Subscribe to the session room
|
|
77
89
|
await this.joinChannel(sessionId, onMessageReceived, tenantId);
|
|
78
90
|
} catch (error) {
|
|
@@ -108,11 +120,17 @@ export class ClientAblyService {
|
|
|
108
120
|
this.channel.subscribe('ReceiveMessage', (message) => {
|
|
109
121
|
try {
|
|
110
122
|
// Ensure messageContent is always a string (default to empty string if undefined)
|
|
123
|
+
const rawData = message.data;
|
|
124
|
+
|
|
111
125
|
const messageContent =
|
|
112
|
-
typeof
|
|
113
|
-
const senderType =
|
|
114
|
-
const needsAgent =
|
|
115
|
-
const attachments =
|
|
126
|
+
typeof rawData === 'string' ? rawData : rawData?.content ?? rawData?.message ?? '';
|
|
127
|
+
const senderType = rawData?.senderType || 3; // Assistant
|
|
128
|
+
const needsAgent = rawData?.needsAgent || rawData?.actionType == 'needs_agent' || false;
|
|
129
|
+
const attachments = rawData?.attachments || [];
|
|
130
|
+
const actionType = (rawData && typeof rawData.actionType === 'string' ? rawData.actionType : '') as
|
|
131
|
+
| string
|
|
132
|
+
| undefined
|
|
133
|
+
| null;
|
|
116
134
|
|
|
117
135
|
// Extract downloadUrl from attachments (Ably now returns downloadUrl directly)
|
|
118
136
|
// Attachments can be: strings (URLs), objects with downloadUrl, or objects with id
|
|
@@ -133,6 +151,19 @@ export class ClientAblyService {
|
|
|
133
151
|
})
|
|
134
152
|
.filter((url: string | null): url is string => url !== null);
|
|
135
153
|
|
|
154
|
+
// Invoke optional action handler first (non-blocking for message processing)
|
|
155
|
+
if (this.onActionReceived && actionType !== undefined) {
|
|
156
|
+
try {
|
|
157
|
+
void this.onActionReceived(actionType, rawData);
|
|
158
|
+
} catch (actionError) {
|
|
159
|
+
console.error('[AblyService] Error in action handler callback', {
|
|
160
|
+
error: actionError,
|
|
161
|
+
actionType,
|
|
162
|
+
rawData,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
136
167
|
onMessageReceived(messageContent, senderType, needsAgent, attachmentUrls);
|
|
137
168
|
} catch (error) {
|
|
138
169
|
console.error('[AblyService] Error processing message', { error, message });
|
|
@@ -166,6 +197,7 @@ export class ClientAblyService {
|
|
|
166
197
|
|
|
167
198
|
this.isConnected = false;
|
|
168
199
|
this.sessionId = null;
|
|
200
|
+
this.onActionReceived = null;
|
|
169
201
|
} catch (error) {
|
|
170
202
|
console.error('[AblyService] Error in stopConnection', { error });
|
|
171
203
|
// Reset state even if there's an error
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export type ActionHandler = (messageData: unknown) => void | Promise<void>;
|
|
4
|
+
|
|
5
|
+
export interface ActionHandlerService {
|
|
6
|
+
registerHandler(actionType: string, handler: ActionHandler): void;
|
|
7
|
+
unregisterHandler(actionType: string): void;
|
|
8
|
+
handleAction(actionType: string | undefined | null, messageData: unknown): Promise<void>;
|
|
9
|
+
getRegisteredActions(): string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useActionHandler = (): ActionHandlerService => {
|
|
13
|
+
const handlersRef = useRef<Map<string, ActionHandler>>(new Map());
|
|
14
|
+
|
|
15
|
+
const defaultHandlerRef = useRef<ActionHandler>((messageData) => {
|
|
16
|
+
const message = (messageData as { actionType?: string } | undefined) ?? {};
|
|
17
|
+
const actionType = message.actionType || '';
|
|
18
|
+
|
|
19
|
+
if (actionType) {
|
|
20
|
+
// Log unknown actions for debugging but do not crash
|
|
21
|
+
// eslint-disable-next-line no-console
|
|
22
|
+
console.warn(
|
|
23
|
+
`[ActionHandler] Unknown action type received: "${actionType}". Message data:`,
|
|
24
|
+
messageData
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const registerHandler = useCallback((actionType: string, handler: ActionHandler) => {
|
|
30
|
+
if (!actionType) {
|
|
31
|
+
// eslint-disable-next-line no-console
|
|
32
|
+
console.warn('[ActionHandler] Attempted to register handler with empty action type. Ignored.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (handlersRef.current.has(actionType)) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.warn(
|
|
39
|
+
`[ActionHandler] Handler for action type "${actionType}" already exists. Overwriting with new handler.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handlersRef.current.set(actionType, handler);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const unregisterHandler = useCallback((actionType: string) => {
|
|
47
|
+
if (!actionType) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
handlersRef.current.delete(actionType);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const handleAction = useCallback(
|
|
54
|
+
async (actionType: string | undefined | null, messageData: unknown) => {
|
|
55
|
+
// Ignore empty, null, or undefined action types (no-op)
|
|
56
|
+
if (!actionType || actionType === '') {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handler = handlersRef.current.get(actionType);
|
|
61
|
+
|
|
62
|
+
if (handler) {
|
|
63
|
+
try {
|
|
64
|
+
await handler(messageData);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// eslint-disable-next-line no-console
|
|
67
|
+
console.error(
|
|
68
|
+
`[ActionHandler] Error executing handler for action type "${actionType}":`,
|
|
69
|
+
error
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const defaultHandler = defaultHandlerRef.current;
|
|
76
|
+
if (defaultHandler) {
|
|
77
|
+
try {
|
|
78
|
+
await Promise.resolve(defaultHandler(messageData));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.error(
|
|
82
|
+
`[ActionHandler] Error executing default handler for unknown action type "${actionType}":`,
|
|
83
|
+
error
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const getRegisteredActions = useCallback(() => {
|
|
92
|
+
return Array.from(handlersRef.current.keys());
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
registerHandler,
|
|
97
|
+
unregisterHandler,
|
|
98
|
+
handleAction,
|
|
99
|
+
getRegisteredActions,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
package/src/lib/index.ts
CHANGED
package/src/ui/help-center.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ReviewDialog from '@/ui/review-dialog';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { apiRequest } from '../core/api';
|
|
4
4
|
import { ClientAblyService } from '../core/AblyService';
|
|
5
5
|
import { useLocalTranslation } from '../useLocalTranslation';
|
|
@@ -11,6 +11,7 @@ import HelpPopup from './help-popup';
|
|
|
11
11
|
import { defaultLanguage } from '@/i18n';
|
|
12
12
|
import { LanguageProvider } from '@/lib/LanguageContext';
|
|
13
13
|
import { getThemeStyles } from '@/lib/theme-utils';
|
|
14
|
+
import { useActionHandler } from '@/lib/custom-hooks/useActionHandler';
|
|
14
15
|
|
|
15
16
|
interface HelpCenterProps {
|
|
16
17
|
helpScreenId: string;
|
|
@@ -57,6 +58,8 @@ const HelpCenterContent = ({
|
|
|
57
58
|
const [assistantStatus, setAssistantStatus] = useState('idle');
|
|
58
59
|
const [isReviewDialogOpen, setIsReviewDialogOpen] = useState(false);
|
|
59
60
|
|
|
61
|
+
const actionHandler = useActionHandler();
|
|
62
|
+
|
|
60
63
|
const handleTogglePopup = () => {
|
|
61
64
|
setIsOpen(!isOpen);
|
|
62
65
|
setShowArrowAnimation(isOpen);
|
|
@@ -93,7 +96,7 @@ const HelpCenterContent = ({
|
|
|
93
96
|
setAssistantStatus('idle');
|
|
94
97
|
};
|
|
95
98
|
|
|
96
|
-
const handleEndChat = async () => {
|
|
99
|
+
const handleEndChat = useCallback(async () => {
|
|
97
100
|
if (!sessionId || !selectedOption) return;
|
|
98
101
|
|
|
99
102
|
try {
|
|
@@ -123,7 +126,7 @@ const HelpCenterContent = ({
|
|
|
123
126
|
setSessionId(null);
|
|
124
127
|
setSelectedOption(null);
|
|
125
128
|
}
|
|
126
|
-
};
|
|
129
|
+
}, [language, selectedOption, sessionId]);
|
|
127
130
|
|
|
128
131
|
const handleSendChatReview = async ({ comment, rating }: ReviewProps) => {
|
|
129
132
|
if (!reviewSessionId) return;
|
|
@@ -195,7 +198,10 @@ const HelpCenterContent = ({
|
|
|
195
198
|
newSessionId,
|
|
196
199
|
responseData.ablyToken,
|
|
197
200
|
handleReceiveMessage,
|
|
198
|
-
responseData.chatSession.tenantId
|
|
201
|
+
responseData.chatSession.tenantId,
|
|
202
|
+
(actionType, messageData) => {
|
|
203
|
+
void actionHandler.handleAction(actionType, messageData);
|
|
204
|
+
}
|
|
199
205
|
);
|
|
200
206
|
|
|
201
207
|
// Verify the connection is actually active
|
|
@@ -297,6 +303,25 @@ const HelpCenterContent = ({
|
|
|
297
303
|
}
|
|
298
304
|
};
|
|
299
305
|
|
|
306
|
+
// Register known action handlers for realtime messages
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
// "needs_agent" → trigger handoff / needs agent UI flow
|
|
309
|
+
actionHandler.registerHandler('needs_agent', () => {
|
|
310
|
+
setNeedsAgent(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// "end_session" → gracefully close the chat session and show review dialog
|
|
314
|
+
actionHandler.registerHandler('end_session', () => {
|
|
315
|
+
// Reuse existing end chat logic
|
|
316
|
+
void handleEndChat();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return () => {
|
|
320
|
+
actionHandler.unregisterHandler('needs_agent');
|
|
321
|
+
actionHandler.unregisterHandler('end_session');
|
|
322
|
+
};
|
|
323
|
+
}, [actionHandler, handleEndChat]);
|
|
324
|
+
|
|
300
325
|
useEffect(() => {
|
|
301
326
|
if (isOpen && helpScreenId) {
|
|
302
327
|
setStatus('loading');
|
package/src/.DS_Store
DELETED
|
Binary file
|