widget-chatbot 1.0.0 → 1.0.1

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.
@@ -1,287 +0,0 @@
1
- import React, { memo, useState, useMemo, useEffect } from 'react';
2
- import { Button, Tooltip, Upload } from 'antd';
3
- import TextareaAutosize from 'react-textarea-autosize';
4
- import AttachmentIcon from 'assets/icons/AttachmentIcon';
5
- import DeleteUploadIcon from 'assets/icons/DeleteUploadIcon';
6
- import LoadingLiveChatIcon from 'assets/LoadingLiveChatIcon';
7
- import Send from 'assets/send_button';
8
- import isStringNull from './helpers/isStringNull';
9
- import { useHelpdeskChatContext } from '../../HelpdeskChatContextProvider';
10
- import useFetch from '../../../../../../hooks/useFetch';
11
- import { useConversationContext } from '../../../../../../context/ConversationContextProvider';
12
- import ModalResult from '../../../../../../helpers/ModalResult';
13
- import getBase64 from '../../../../../../helpers/getBase64';
14
- import getTextWidth from '../../../../../../helpers/getTextWidth';
15
- import { useErrorBoundaryContext } from '../../../../../../context/ErrorBoundaryContextProvider';
16
- import useDictionary from '../../../../../../hooks/useDictionary';
17
- import { usePayloadContext } from '../../../../../../PayloadContextProvider';
18
-
19
- export const WrapperFileName = ({ fileName, children }) => {
20
- const widthFileName = getTextWidth(fileName);
21
-
22
- if (widthFileName > 250) {
23
- return <Tooltip title={fileName}>{children}</Tooltip>;
24
- }
25
-
26
- return children;
27
- };
28
-
29
- const AttachmentField = () => {
30
- const { chatState, setChatState } = useHelpdeskChatContext();
31
- const [urlImage, setUrlImg] = useState('');
32
-
33
- const isImage = useMemo(() => {
34
- return chatState?.fileInput?.type?.includes('image');
35
- }, [chatState?.fileInput]);
36
-
37
- const onConvertImgToBase64 = async () => {
38
- const base64Img = await getBase64(chatState?.fileInput);
39
-
40
- setUrlImg(base64Img);
41
- };
42
-
43
- useEffect(() => {
44
- if (chatState?.fileInput?.uid) {
45
- onConvertImgToBase64();
46
- }
47
- }, [chatState?.fileInput?.uid]);
48
-
49
- useEffect(() => {
50
- // Monitor socket connection status
51
- if (socket) {
52
- console.log("Socket connection status:", socket.connected);
53
- }
54
- }, [socket]);
55
-
56
- return (
57
- <div className="rw-file-name">
58
- <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
59
- {isImage ? (
60
- <img
61
- src={urlImage}
62
- style={{ width: 52, cursor: 'pointer' }}
63
- onClick={(e) => {
64
- e?.stopPropagation();
65
-
66
- setChatState((prev) => ({
67
- ...prev,
68
- isShowModalPreviewFile: true,
69
- urlImagePreview: urlImage,
70
- }));
71
- }}
72
- />
73
- ) : (
74
- <AttachmentIcon className="rw-attachment-icon-list" />
75
- )}
76
- <WrapperFileName fileName={chatState?.fileInput?.name}>
77
- <p className="rw-file-name-text">{chatState?.fileInput?.name}</p>
78
- </WrapperFileName>
79
- </div>
80
- <Button
81
- style={{ color: 'rgba(0, 0, 0, 0.45)' }}
82
- icon={<DeleteUploadIcon />}
83
- onClick={() => {
84
- setChatState((prev) => ({
85
- ...prev,
86
- fileInput: null,
87
- }));
88
- }}
89
- />
90
- </div>
91
- );
92
- };
93
-
94
- const HelpdeskSender = () => {
95
- const [inputVal, setInputVal] = useState('');
96
- const { socket } = usePayloadContext(); // Tambahkan ini jika belum ada
97
- const { taskIDSelect, connected, isBackClick } = useConversationContext();
98
- const { setChatState, chatState } = useHelpdeskChatContext();
99
- const { isErrorBoundary } = useErrorBoundaryContext();
100
-
101
- const LabelTextFieldHint = useDictionary('TypeMessage', 'Type a message');
102
-
103
- const inputValDOM = document.querySelector('.rw-new-message')?.value;
104
-
105
- const fetch = useFetch();
106
-
107
- const isDisabledInput = useMemo(() => {
108
- return (
109
- isErrorBoundary ||
110
- !connected ||
111
- ((isStringNull(inputValDOM) || !chatState?.fileInput) && chatState?.loadingSend)
112
- );
113
- }, [inputValDOM, chatState?.fileInput, chatState?.loadingSend, connected]);
114
-
115
- const classNameHelpdesk = useMemo(() => {
116
- if (isBackClick) {
117
- return 'rw-container-helpdesk-sender-from-detail';
118
- }
119
- return 'rw-container-helpdesk-sender';
120
- }, [isBackClick]);
121
-
122
- // HelpdeskSender.js - onSendMessage yang direvisi
123
- const onSendMessage = () => {
124
- if (!socket?.connected) {
125
- console.warn("Socket not connected!");
126
- ModalResult({
127
- type: 'error',
128
- error: new Error("Connection lost. Please refresh the page."),
129
- });
130
- return;
131
- }
132
-
133
- setChatState((prev) => ({
134
- ...prev,
135
- loadingSend: true,
136
- }));
137
-
138
- fetch({
139
- endpoint: 'v1/sf7/general/helpdesk/chat/submit',
140
- data: {
141
- TASK_ID: taskIDSelect,
142
- ATTACHMENT: '',
143
- FAQ_ID: null,
144
- MESSAGE: inputVal,
145
- },
146
- })
147
- ?.then(() => {
148
- console.log("Message sent successfully");
149
- setInputVal('');
150
- // Clear input field
151
- const messageInput = document.querySelector('.rw-new-message');
152
- if (messageInput) {
153
- messageInput.value = '';
154
- }
155
- // Reset loading state after successful send
156
- setChatState((prev) => ({
157
- ...prev,
158
- loadingSend: false
159
- }));
160
- })
161
- ?.catch((error) => {
162
- console.error("Error sending message:", error);
163
- ModalResult({
164
- type: 'error',
165
- error,
166
- });
167
- setChatState((prev) => ({
168
- ...prev,
169
- loadingSend: false,
170
- }));
171
- });
172
- };
173
-
174
- const onSendFile = (url) => {
175
- fetch({
176
- endpoint: 'v1/sf7/general/helpdesk/chat/submit',
177
- data: {
178
- ATTACHMENT: url,
179
- FAQ_ID: null,
180
- TASK_ID: taskIDSelect,
181
- },
182
- })?.catch((error) => {
183
- ModalResult({
184
- type: 'error',
185
- error,
186
- });
187
- setChatState((prev) => ({
188
- ...prev,
189
- loadingSend: false,
190
- }));
191
- });
192
- };
193
-
194
- const onGetURL = () => {
195
- setChatState((prev) => ({
196
- ...prev,
197
- loadingSend: true,
198
- }));
199
-
200
- const formData = new FormData();
201
-
202
- formData?.append('FILE', chatState?.fileInput);
203
- formData?.append('UPLOAD_CODE', 'helpdesk');
204
- formData?.append('UPLOAD_TYPE', 3);
205
-
206
- fetch({
207
- endpoint: 'v1/sf7/hrm/upload/temp',
208
- data: formData,
209
- isFormData: true,
210
- })?.then(({ data }) => {
211
- const urlAttachment = data?.DATA?.LIST?.TEMP_PATH;
212
- onSendFile(urlAttachment);
213
- });
214
- };
215
-
216
- const onEnterSendMsg = (e) => {
217
- if (!isErrorBoundary && e?.keyCode === 13 && !isStringNull(inputVal) && !e.shiftKey) {
218
- e.preventDefault();
219
- onSendMessage();
220
- }
221
- };
222
-
223
- return (
224
- <div className={`${classNameHelpdesk} rw-container-sender`}>
225
- {chatState?.fileInput ? (
226
- <div className="rw-filelist-container">
227
- <AttachmentField />
228
- </div>
229
- ) : (
230
- <TextareaAutosize
231
- type="text"
232
- minRows={2}
233
- onKeyDown={onEnterSendMsg}
234
- maxRows={5}
235
- onChange={({ target: { value } }) => {
236
- setInputVal(value);
237
- }}
238
- className="rw-new-message"
239
- name="message"
240
- autoFocus
241
- autoComplete="off"
242
- placeholder={`${LabelTextFieldHint}...`}
243
- />
244
- )}
245
-
246
- <div className="rw-container-btn-send">
247
- <Upload
248
- className="btn_upload"
249
- beforeUpload={() => false}
250
- fileList={[]}
251
- accept=".doc, .jpg, .ods, .png, .txt, .docx, .pdf"
252
- onChange={(e) => {
253
- setChatState((prev) => ({
254
- ...prev,
255
- fileInput: e?.file,
256
- }));
257
- }}
258
- >
259
- <div className="rw-new-topic">
260
- <AttachmentIcon />
261
- Attachment
262
- </div>
263
- </Upload>
264
-
265
- <button
266
- onClick={() => {
267
- if (chatState?.fileInput) {
268
- onGetURL();
269
- } else if (!isStringNull(inputVal)) {
270
- onSendMessage();
271
- }
272
- }}
273
- type="submit"
274
- className="rw-send"
275
- disabled={isDisabledInput}
276
- >
277
- {chatState?.loadingSend ? (
278
- <LoadingLiveChatIcon />
279
- ) : (
280
- <Send ready={!isDisabledInput} className="rw-send-icon" alt="send" />
281
- )}
282
- </button>
283
- </div>
284
- </div>
285
- );
286
- };
287
- export default memo(HelpdeskSender);
@@ -1,131 +0,0 @@
1
- import axios from 'axios';
2
- import React, { useState, useMemo, useContext, createContext, useEffect } from 'react';
3
- import { BASE_WEBSOCKET_URL } from './helpers/constant';
4
- import { io } from 'socket.io-client';
5
-
6
- /** @type {import('react').Context<PayloadContextData>} */
7
- const PayloadContext = createContext({});
8
-
9
- export const usePayloadContext = () => useContext(PayloadContext);
10
-
11
- /**
12
- *
13
- * @param {object} props
14
- * @param {string} props.initPayload
15
- * @returns
16
- */
17
- const PayloadContextProvider = ({ children, initPayload, setInitPayload }) => {
18
- /** @type {PayloadContextData} */
19
- const objInitPayload = useMemo(() => {
20
- return JSON.parse(initPayload?.replace('/get_started', '') || '{}');
21
- }, [initPayload]);
22
-
23
- const [isRefetch, setRefetch] = useState(false);
24
- const [isVerifyHelpdesk, setVerifyHelpdesk] = useState(false);
25
- const [isLoadedAccessWS, setLoadedAccessWS] = useState(false);
26
- const [socket, setSocket] = useState(null);
27
-
28
- // Initialize the Socket.io client
29
- useEffect(() => {
30
- const newSocket = io(BASE_WEBSOCKET_URL);
31
- setSocket(newSocket);
32
-
33
- return () => {
34
- newSocket.disconnect();
35
- };
36
- }, []);
37
-
38
- const onRefreshToken = async () => {
39
- try {
40
- const response = await axios({
41
- url: objInitPayload?.uriBackend,
42
- params: {
43
- ofid: 'sfsystem.RefreshToken',
44
- accname: objInitPayload?.companycode,
45
- },
46
- data: {
47
- refreshtoken:
48
- objInitPayload?.refresh_token ||
49
- '4D8C30EE29749D0F06B4F6E2527516B397C09D4DB148D42214E2941C7A86139D3CB512D925E1B098C6090A45E6BCC545723222FA3BABDC2A',
50
- },
51
- method: 'POST',
52
- });
53
-
54
- if (response?.data?.DATA) {
55
- const data = response?.data?.DATA;
56
-
57
- let payload = '/get_started';
58
-
59
- payload += JSON.stringify({
60
- access_token: data?.JWT_TOKEN,
61
- refresh_token: objInitPayload?.refresh_token,
62
- uagent: objInitPayload?.uagent,
63
- uriBackend: objInitPayload?.uriBackend,
64
- uriService: objInitPayload?.uriService,
65
- companyid: objInitPayload?.companyid,
66
- companycode: objInitPayload?.companycode,
67
- lang: objInitPayload?.lang,
68
- uriBackendEnt: objInitPayload?.uriBackendEnt,
69
- });
70
-
71
- setInitPayload(payload);
72
-
73
- return data?.JWT_TOKEN;
74
- } else {
75
- throw new Error('Failed to refresh token');
76
- }
77
- } catch (err) {
78
- throw err;
79
- }
80
- };
81
-
82
- // Handle Socket.io events
83
- useEffect(() => {
84
- if (!socket) return;
85
-
86
- const { companyid, companycode, lang, uagent, access_token, uriBackendEnt, uriBackend } =
87
- objInitPayload;
88
-
89
- const messageHandshake = {
90
- coid: companyid,
91
- cocode: companycode,
92
- lang,
93
- uagent,
94
- jwt: access_token,
95
- uribackendent: uriBackendEnt,
96
- accname: companycode?.toUpperCase(),
97
- uribackendlucee: uriBackend,
98
- };
99
-
100
- socket.emit('access_ws', messageHandshake);
101
-
102
- socket.on('access_ws_response', (data) => {
103
- if (!isLoadedAccessWS) {
104
- setLoadedAccessWS(true);
105
- setVerifyHelpdesk(data?.result);
106
- }
107
- });
108
-
109
- // Cleanup on component unmount
110
- return () => {
111
- socket.off('access_ws_response');
112
- };
113
- }, [socket, initPayload]);
114
-
115
- return (
116
- <PayloadContext.Provider
117
- value={{
118
- ...objInitPayload,
119
- onRefreshToken,
120
- isRefetch,
121
- setRefetch,
122
- socket,
123
- isVerifyHelpdesk,
124
- }}
125
- >
126
- {children}
127
- </PayloadContext.Provider>
128
- );
129
- };
130
-
131
- export default PayloadContextProvider;
@@ -1,28 +0,0 @@
1
- import CryptoJS from 'crypto-js';
2
- import isMobileUserAgent from './isMobileUserAgent';
3
-
4
- export const IS_MOBILE = isMobileUserAgent();
5
- // && isWorkplazeMobile();
6
-
7
- // export const KEY = process?.env?.REACT_APP_KEY || '6Le0DgMTAAAANokdEEial';
8
- export const KEY = '6Le0DgMTAAAANokdEEial';
9
-
10
- export const APP_KEY_HEX = (keyEncrypt) =>
11
- CryptoJS.enc.Hex.parse(CryptoJS.enc.Utf8.parse(keyEncrypt || KEY).toString());
12
-
13
- export const CIPHER_OPTIONS = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 };
14
-
15
- export const ENCRYPTED_ROUTE_PREFIX = '|';
16
- export const UNENCRYPTED_ROUTE_PREFIX = '#';
17
-
18
- // export const = 'wss://sfchatbot.dataon.com:5005/livechat-he lpdesk/ws';
19
-
20
- // export const BASE_WEBSOCKET_URL = 'ws://localhost:8002/livechat-helpdesk/ws';
21
- export const BASE_WEBSOCKET_URL = "http://localhost:5100/livechat-helpdesk/ws";
22
-
23
-
24
-
25
-
26
-
27
-
28
-
@@ -1,178 +0,0 @@
1
- import { useEffect, useRef } from 'react';
2
- import { usePayloadContext } from '../PayloadContextProvider';
3
- import sortArr from '../helpers/sortArr';
4
- import { scrollToBottom } from '../components/Widget/components/Conversation/components/Messages';
5
- import uniqueArrObj from '../helpers/uniqueArrObj';
6
- import useDecodeJWT from './useDecodeJWT';
7
- import formatChat, { deFormatChat } from '../helpers/formatChat';
8
- import { useConversationContext } from '../context/ConversationContextProvider';
9
- import { useHelpdeskChatContext } from '../components/Widget/components/Conversation/HelpdeskChatContextProvider';
10
- import { useListTicketContext } from '../context/ListTicketContextProvider';
11
- import ModalResult from '../helpers/ModalResult';
12
-
13
- const useWSChatbot = () => {
14
- const { socket } = usePayloadContext();
15
- const { taskIDSelect } = useConversationContext();
16
- const { ticketState, taskIDs, setTicketState } = useListTicketContext();
17
- const { chatState, setChatState, isFetchPrevChat } = useHelpdeskChatContext();
18
- const { EMP_ID } = useDecodeJWT();
19
-
20
- // Refs untuk mengakses state terbaru
21
- const chatStateRef = useRef(chatState);
22
- const ticketStateRef = useRef(ticketState);
23
- const isFetchPrevChatRef = useRef(isFetchPrevChat);
24
-
25
- useEffect(() => {
26
- chatStateRef.current = chatState;
27
- }, [chatState]);
28
-
29
- useEffect(() => {
30
- ticketStateRef.current = ticketState;
31
- }, [ticketState]);
32
-
33
- useEffect(() => {
34
- isFetchPrevChatRef.current = isFetchPrevChat;
35
- }, [isFetchPrevChat]);
36
-
37
- const handleNewNotif = (data) => {
38
- console.log("New Notification Data:", data);
39
- const newTicket = data?.filter((val) => val?.newChat);
40
-
41
- setTimeout(() => {
42
- setTicketState((prev) => ({
43
- ...prev,
44
- count: newTicket?.length,
45
- }));
46
- }, 250);
47
- };
48
-
49
- const handleNewChat = (data) => {
50
- try {
51
- console.log("Processing new chat data:", data);
52
- if (!Array.isArray(data)) {
53
- console.warn("Received non-array data in handleNewChat:", data);
54
- return;
55
- }
56
-
57
- const newChat = data;
58
- const deFormatOldChat = deFormatChat(chatStateRef.current?.list || []);
59
- console.log("Current chat list:", deFormatOldChat);
60
-
61
- const arrNewChat = [...deFormatOldChat, ...newChat];
62
- console.log("Combined chat list:", arrNewChat);
63
-
64
- const sortNewChat = uniqueArrObj(sortArr(arrNewChat, 'ID'), 'ID');
65
- console.log("Sorted and deduplicated chat list:", sortNewChat);
66
-
67
- const unreadChat = newChat?.filter(
68
- (item) => item?.READ_BY === '' && item?.ACTOR?.EMP_ID !== EMP_ID
69
- );
70
-
71
- setTicketState((prev) => ({
72
- ...prev,
73
- count: unreadChat?.length,
74
- }));
75
-
76
- setChatState((prev) => {
77
- console.log("Updating chat state with new list");
78
- return {
79
- ...prev,
80
- loadingSend: false,
81
- ...(prev.loadingInit && {
82
- loadingInit: false,
83
- }),
84
- list: formatChat(sortNewChat),
85
- fileInput: null,
86
- };
87
- });
88
-
89
- if (!isFetchPrevChatRef.current) {
90
- console.log("Scrolling to bottom");
91
- scrollToBottom();
92
- }
93
- } catch (error) {
94
- console.error("Error in handleNewChat:", error);
95
- }
96
- };
97
-
98
- useEffect(() => {
99
- if (!socket) return;
100
-
101
- console.log("Setting up WebSocket listeners");
102
-
103
- // Debug socket connection
104
- socket.on('connect', () => {
105
- console.log('WebSocket connected');
106
- });
107
-
108
- socket.on('disconnect', () => {
109
- console.log('WebSocket disconnected');
110
- });
111
-
112
- // Listener untuk new_notif
113
- socket.on('new_notif', handleNewNotif);
114
-
115
- // Listener untuk new_chat
116
- socket.on('new_chat', (data) => {
117
- console.log("Received new_chat event:", data);
118
- handleNewChat(data);
119
- });
120
-
121
- // Clean up listeners saat komponen unmount
122
- return () => {
123
- console.log("Cleaning up WebSocket listeners");
124
- socket.off('new_notif', handleNewNotif);
125
- socket.off('new_chat', handleNewChat);
126
- };
127
- }, [socket, EMP_ID]); // Hapus chatState, ticketState, dan isFetchPrevChat
128
-
129
- const onWatchNotif = () => {
130
- const params = { task_id_list: taskIDs };
131
- console.log("Watching Notifications:", params);
132
-
133
- if (!ticketState?.isInitNotif) {
134
- socket?.emit('load_notif', params);
135
- console.log("Notification Event Sent to Server:", params);
136
-
137
- setTimeout(() => {
138
- setTicketState((prev) => ({
139
- ...prev,
140
- isInitNotif: true,
141
- }));
142
- }, 350);
143
- }
144
- };
145
-
146
- const onWatchLiveChat = () => {
147
- const params = {
148
- taskid: taskIDSelect,
149
- list_chat_id: deFormatChat(chatStateRef.current?.list || [])?.map((item) => item?.ID),
150
- };
151
- console.log("Watching Live Chat:", params);
152
-
153
- if (deFormatChat(chatStateRef.current?.list || [])?.length > 0) {
154
- if (!chatStateRef.current?.isInitChat) {
155
- setChatState((prev) => ({
156
- ...prev,
157
- isInitChat: true,
158
- }));
159
- }
160
- socket?.emit('live_chat', params);
161
- console.log("Live Chat Event Sent to Server:", params);
162
- }
163
- };
164
-
165
- const onStopNotif = () => {
166
- socket?.emit('stop_notif');
167
- console.log("Stopping Notifications");
168
- };
169
-
170
- const onStopLiveChat = () => {
171
- socket?.emit('stop_live_chat');
172
- console.log("Stopping Live Chat");
173
- };
174
-
175
- return { onStopNotif, onStopLiveChat, onWatchNotif, onWatchLiveChat };
176
- };
177
-
178
- export default useWSChatbot;
package/npmrc.enc DELETED
Binary file
@@ -1,64 +0,0 @@
1
- # Publish Process
2
-
3
- ## Publish
4
-
5
- ### Development and Testing
6
-
7
- - Update version
8
-
9
- ```sh
10
- npm version 1.x.x-alpha.x
11
- ```
12
-
13
- - Publish
14
-
15
- ```sh
16
- npm publish --otp=<otpCode> --tag alpha.x
17
- ```
18
-
19
- ### Production
20
-
21
- - Update version
22
-
23
- ```sh
24
- npm version 1.x.x
25
- ```
26
-
27
- - Publish
28
-
29
- ```sh
30
- npm publish --otp=<otpCode>
31
- ```
32
-
33
- ## Unpublish
34
-
35
- If published package less than 72 hours and you want to revert/unpublish the chatbot package, try this command.
36
-
37
- ```sh
38
- npm unpublish custom-chatbot-widget@1.x.x
39
- ```
40
-
41
- ## Some Important Notes
42
-
43
- ### Versi Stabil vs. Prerelease
44
-
45
- Stable versions (a.b.c) cannot be republished after being unpublished because NPM retains the metadata of stable versions permanently.
46
-
47
- For prerelease versions (a.b.c-alpha.x), the metadata is not stored permanently after unpublishing, so you can republish the same version.
48
-
49
- You must first unpublish the version a.b.c-alpha.x.
50
- As long as the version no longer exists in the registry, you can republish it with the same version number.
51
-
52
- ### Check published version
53
-
54
- To check all versions that may not be displayed in the NPM registry
55
-
56
- ```sh
57
- npm info custom-chatbot-widget --json
58
- ```
59
-
60
- To check all versions that are already displayed in the NPM registry
61
-
62
- ```sh
63
- npm view custom-chatbot-widget versions
64
- ```