shopify-chatbot-widget 0.0.4
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/README.md +69 -0
- package/eslint.config.js +23 -0
- package/index.html +13 -0
- package/package-lock 2.json +4493 -0
- package/package.json +43 -0
- package/public/vite.svg +1 -0
- package/src/App.css +42 -0
- package/src/App.tsx +13 -0
- package/src/Chatbot/Chatbot.css +154 -0
- package/src/Chatbot/Chatbot.tsx +207 -0
- package/src/Chatbot/components/ChatInput.css +127 -0
- package/src/Chatbot/components/ChatInput.tsx +262 -0
- package/src/Chatbot/components/Messages/Addcart.tsx +70 -0
- package/src/Chatbot/components/Messages/AgentStatus.tsx +37 -0
- package/src/Chatbot/components/Messages/AssistantMesage.tsx +316 -0
- package/src/Chatbot/components/Messages/AudioMessage.tsx +123 -0
- package/src/Chatbot/components/Messages/CartWidget.tsx +116 -0
- package/src/Chatbot/components/Messages/ChatImage.tsx +46 -0
- package/src/Chatbot/components/Messages/ChatImageText.tsx +74 -0
- package/src/Chatbot/components/Messages/ChatImageTextAssis.tsx +72 -0
- package/src/Chatbot/components/Messages/ChatMessages.css +5 -0
- package/src/Chatbot/components/Messages/ChatMessages.tsx +284 -0
- package/src/Chatbot/components/Messages/Support.tsx +177 -0
- package/src/Chatbot/components/Messages/Typing.css +27 -0
- package/src/Chatbot/components/Messages/Typing.tsx +14 -0
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +142 -0
- package/src/Chatbot/components/Messenger/Messenger.tsx +521 -0
- package/src/Chatbot/components/Notification.tsx +27 -0
- package/src/Chatbot/components/Powered.css +16 -0
- package/src/Chatbot/components/Powered.tsx +13 -0
- package/src/Chatbot/components/Sessions/CreateSession.tsx +139 -0
- package/src/Chatbot/components/Sessions/RenderList.tsx +202 -0
- package/src/Chatbot/components/Suggestions.css +34 -0
- package/src/Chatbot/components/Suggestions.tsx +28 -0
- package/src/assets/react.svg +1 -0
- package/src/chatbot-widget/index.tsx +14 -0
- package/src/hooks/config.tsx +13 -0
- package/src/hooks/useCart.tsx +99 -0
- package/src/hooks/useChatbotAPI.tsx +160 -0
- package/src/hooks/useChatbotData.tsx +129 -0
- package/src/hooks/useSessionMessages.tsx +51 -0
- package/src/hooks/useWebsocke.tsx +45 -0
- package/src/i18n.tsx +30 -0
- package/src/index.css +1 -0
- package/src/main.tsx +10 -0
- package/src/types/chatbot.ts +38 -0
- package/src/utils/cookies.tsx +14 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +27 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +25 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import React, { useState,useCallback } from 'react';
|
|
2
|
+
import { Button, Avatar } from 'antd';
|
|
3
|
+
import { CloseOutlined,ArrowLeftOutlined } from '@ant-design/icons';
|
|
4
|
+
import '../../Chatbot.css';
|
|
5
|
+
import { useChatbotMessage } from '../../../hooks/useSessionMessages';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import ChatInput from '../../components/ChatInput';
|
|
8
|
+
import Suggestions from '../../components/Suggestions';
|
|
9
|
+
import Powered from '../../components/Powered';
|
|
10
|
+
import ChatMessages from '../../components/Messages/ChatMessages';
|
|
11
|
+
import config from '../../../hooks/config';
|
|
12
|
+
import { useChatbotAPI } from '../../../hooks/useChatbotAPI';
|
|
13
|
+
import type { ChatMessage } from '../../../types/chatbot';
|
|
14
|
+
import { getCookie } from '../../../utils/cookies';
|
|
15
|
+
import useChatSocket from '../../../hooks/useWebsocke';
|
|
16
|
+
import Notification from '../Notification';
|
|
17
|
+
import CartWidget from '../Messages/CartWidget';
|
|
18
|
+
import { useCart } from '../../../hooks/useCart';
|
|
19
|
+
|
|
20
|
+
type MessengerProps = {
|
|
21
|
+
chatbotLogo: string;
|
|
22
|
+
userType:string;
|
|
23
|
+
agentName:string;
|
|
24
|
+
colorCode: string;
|
|
25
|
+
newSession: boolean;
|
|
26
|
+
messagesSuggestions: string[];
|
|
27
|
+
disableForm: boolean;
|
|
28
|
+
sessionId:string;
|
|
29
|
+
setOpen: (v: boolean) => void;
|
|
30
|
+
setView: (v: string) => void;
|
|
31
|
+
setNewSession: (v: boolean) => void;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Opened: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface UploadImageResponse {
|
|
38
|
+
image_url: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
interface UploadAudioResponse {
|
|
43
|
+
audio_url: string;
|
|
44
|
+
transcription_text:string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const Messenger: React.FC<MessengerProps> = ({
|
|
48
|
+
chatbotLogo,
|
|
49
|
+
agentName,
|
|
50
|
+
colorCode,
|
|
51
|
+
newSession,
|
|
52
|
+
userType,
|
|
53
|
+
setNewSession,
|
|
54
|
+
messagesSuggestions,
|
|
55
|
+
disableForm,
|
|
56
|
+
sessionId,
|
|
57
|
+
setOpen,
|
|
58
|
+
Opened,
|
|
59
|
+
setView
|
|
60
|
+
}) => {
|
|
61
|
+
const [input, setInput] = useState('');
|
|
62
|
+
const [selectedImage, setSelectedImage] = useState<File | null>(null);
|
|
63
|
+
const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
|
|
64
|
+
const [isSending, setIsSending] = useState(false);
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const [, setNewUser] = useState(() => {
|
|
70
|
+
const cookieValue = getCookie('new_user');
|
|
71
|
+
return cookieValue === 'true';
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const [isChatlogCreated, setIsChatlogCreated] = useState(false);
|
|
76
|
+
const [, setChatLogWaiting] = useState(false);
|
|
77
|
+
const [createNew, setCreateNew] = useState(false);
|
|
78
|
+
const [, setSessionCreated] = useState(false);
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const { messages, setMessages } = useChatbotMessage(sessionId);
|
|
82
|
+
;
|
|
83
|
+
const [agentStatus, setAgentStatus] = useState<{ status?: string; typing?: boolean; user?: string }>({});
|
|
84
|
+
const [notification, setNotification] = useState<string | null>(null);
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
const handleSocketData = useCallback(
|
|
91
|
+
(data: any) => {
|
|
92
|
+
if (data.status) {
|
|
93
|
+
setAgentStatus({
|
|
94
|
+
status: data.status,
|
|
95
|
+
user: data.user || 'Support',
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const { additionalData = {}, message, image_url } = data;
|
|
100
|
+
if (!additionalData.isBusiness && additionalData.sender !== 'AI') {
|
|
101
|
+
setMessages((prev) => [
|
|
102
|
+
...prev,
|
|
103
|
+
{
|
|
104
|
+
role: 'assistant',
|
|
105
|
+
content: message,
|
|
106
|
+
isBusiness: false,
|
|
107
|
+
sender: additionalData.sender,
|
|
108
|
+
image_url,
|
|
109
|
+
} as ChatMessage,
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[setMessages]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const socket = useChatSocket(sessionId, handleSocketData);
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
const { apiCall } = useChatbotAPI({
|
|
121
|
+
disableChatbot: false,
|
|
122
|
+
disableChatbotGlobally: false,
|
|
123
|
+
isChatlogCreated,
|
|
124
|
+
createNew,
|
|
125
|
+
disableForm: disableForm,
|
|
126
|
+
calendlyLink: null,
|
|
127
|
+
socket:socket,
|
|
128
|
+
newSession:newSession,
|
|
129
|
+
setMessages,
|
|
130
|
+
setIsChatlogCreated,
|
|
131
|
+
setChatLogWaiting,
|
|
132
|
+
setNewUser,
|
|
133
|
+
setCreateNew,
|
|
134
|
+
setSessionCreated,
|
|
135
|
+
setNewSession:setNewSession
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
const sendMessage = async () => {
|
|
141
|
+
if (!input.trim()) return;
|
|
142
|
+
|
|
143
|
+
let message=input
|
|
144
|
+
setInput('');
|
|
145
|
+
setIsSending(true); // 🔒 Disable input
|
|
146
|
+
const userMsg: ChatMessage = { role: 'user', content: message };
|
|
147
|
+
setMessages(prev => [...prev, userMsg]);
|
|
148
|
+
|
|
149
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
150
|
+
const additionalData = { isBusiness: true, SessionId: sessionId };
|
|
151
|
+
const messagePayload = JSON.stringify({
|
|
152
|
+
message: message,
|
|
153
|
+
additionalData: additionalData,
|
|
154
|
+
});
|
|
155
|
+
socket.send(messagePayload);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await apiCall(input, newSession, sessionId);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error(err);
|
|
162
|
+
} finally {
|
|
163
|
+
setInput('');
|
|
164
|
+
setIsSending(false); // 🔓 Re-enable input
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const sendMessageSuggestion = async (message: string): Promise<void> => {
|
|
169
|
+
if (!message.trim()) return;
|
|
170
|
+
|
|
171
|
+
setIsSending(true); // 🔒 Disable input
|
|
172
|
+
|
|
173
|
+
const userMsg: ChatMessage = { role: 'user', content: message };
|
|
174
|
+
setMessages(prev => [...prev, userMsg]);
|
|
175
|
+
|
|
176
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
177
|
+
const additionalData = { isBusiness: true, SessionId: sessionId };
|
|
178
|
+
const messagePayload = JSON.stringify({
|
|
179
|
+
message,
|
|
180
|
+
additionalData,
|
|
181
|
+
});
|
|
182
|
+
socket.send(messagePayload);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
await apiCall(message, newSession, sessionId);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error("Error sending suggestion:", error);
|
|
189
|
+
} finally {
|
|
190
|
+
setInput('');
|
|
191
|
+
setIsSending(false); // 🔓 Re-enable input
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async function handleSendImage (image: File, userMessage:string) {
|
|
198
|
+
const tempMessageId = Date.now();
|
|
199
|
+
const tempImageUrl = URL.createObjectURL(image);
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if (!image) return;
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
const tempJson: ChatMessage = {
|
|
210
|
+
id: tempMessageId,
|
|
211
|
+
role: 'user', // ✅ satisfies union
|
|
212
|
+
content: userMessage || '',
|
|
213
|
+
image_url: tempImageUrl,
|
|
214
|
+
isBusiness: true,
|
|
215
|
+
status: 'uploading',
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
setMessages((prevMessages) => {
|
|
222
|
+
const updatedMessages = [...prevMessages, tempJson];
|
|
223
|
+
return updatedMessages;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
let finalImageUrl: string | null = null;
|
|
228
|
+
|
|
229
|
+
if (image) {
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const formData = new FormData();
|
|
233
|
+
formData.append("image", image);
|
|
234
|
+
|
|
235
|
+
const uploadResponse = await axios.post<UploadImageResponse>(
|
|
236
|
+
`${config.apiUrl}upload-image/`,
|
|
237
|
+
formData
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if (uploadResponse.status === 201 && uploadResponse.data?.image_url) {
|
|
242
|
+
finalImageUrl = uploadResponse.data.image_url;
|
|
243
|
+
setMessages((prevMessages) => {
|
|
244
|
+
const updatedMessages = prevMessages.map((msg) =>
|
|
245
|
+
msg.id === tempMessageId && finalImageUrl
|
|
246
|
+
? { ...msg, image_url: finalImageUrl } // ✅ only if finalImageUrl is not null
|
|
247
|
+
: msg
|
|
248
|
+
);
|
|
249
|
+
return updatedMessages;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error("Error uploading image:", error);
|
|
254
|
+
setMessages((messageList) =>
|
|
255
|
+
messageList.map((msg) =>
|
|
256
|
+
msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
|
|
257
|
+
)
|
|
258
|
+
);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
267
|
+
const additionalData = { isBusiness: true, SessionId: sessionId };
|
|
268
|
+
const messagePayload = JSON.stringify({
|
|
269
|
+
image_url: finalImageUrl || null,
|
|
270
|
+
message: input.trim()||"",
|
|
271
|
+
additionalData: additionalData,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
socket.send(messagePayload);
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
if (finalImageUrl) {
|
|
280
|
+
setMessages((messageList) =>
|
|
281
|
+
messageList.map((msg) =>
|
|
282
|
+
msg.id === tempMessageId ? { ...msg, status: 'delivered' } : msg
|
|
283
|
+
)
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
apiCall(userMessage.trim() || '',newSession,sessionId,finalImageUrl,true)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Error sending Image message:', error);
|
|
294
|
+
|
|
295
|
+
setMessages((messageList) =>
|
|
296
|
+
messageList.map((msg) =>
|
|
297
|
+
msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
|
|
298
|
+
)
|
|
299
|
+
);
|
|
300
|
+
} finally {
|
|
301
|
+
setSelectedImage(null);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
const handleSendVoice = async (audio: Blob) => {
|
|
309
|
+
if (!audio) return;
|
|
310
|
+
|
|
311
|
+
const tempAudioUrl = URL.createObjectURL(audio);
|
|
312
|
+
const tempMessageId = Date.now();
|
|
313
|
+
|
|
314
|
+
// Step 1: Add temporary audio message
|
|
315
|
+
const tempMessage: ChatMessage = {
|
|
316
|
+
id: tempMessageId,
|
|
317
|
+
role: 'user',
|
|
318
|
+
content: '',
|
|
319
|
+
audio_url: tempAudioUrl,
|
|
320
|
+
isBusiness: true,
|
|
321
|
+
status: 'uploading',
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
setMessages(prev => [...prev, tempMessage]);
|
|
325
|
+
|
|
326
|
+
let finalAudioUrl: string | undefined = undefined;
|
|
327
|
+
let transcriptionText: string = '';
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
// Step 2: Upload audio to backend
|
|
331
|
+
const formData = new FormData();
|
|
332
|
+
formData.append('audio', audio);
|
|
333
|
+
|
|
334
|
+
const uploadResponse = await axios.post<UploadAudioResponse>(
|
|
335
|
+
`${config.apiUrl}upload-audio/`,
|
|
336
|
+
formData
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
if (uploadResponse.status === 201) {
|
|
345
|
+
finalAudioUrl = uploadResponse.data.audio_url;
|
|
346
|
+
transcriptionText = uploadResponse.data.transcription_text || '[Audio]';
|
|
347
|
+
|
|
348
|
+
// Step 3: Replace temp audio URL with final URL
|
|
349
|
+
setMessages(prev =>
|
|
350
|
+
prev.map(msg =>
|
|
351
|
+
msg.id === tempMessageId
|
|
352
|
+
? {
|
|
353
|
+
...msg,
|
|
354
|
+
audio_url: finalAudioUrl,
|
|
355
|
+
content: transcriptionText,
|
|
356
|
+
status: 'delivered',
|
|
357
|
+
}
|
|
358
|
+
: msg
|
|
359
|
+
)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error('Audio upload failed:', error);
|
|
364
|
+
setMessages(prev =>
|
|
365
|
+
prev.map(msg =>
|
|
366
|
+
msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
// Step 4: Send to WebSocket
|
|
374
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
375
|
+
const payload = JSON.stringify({
|
|
376
|
+
audio_url: finalAudioUrl,
|
|
377
|
+
message: transcriptionText,
|
|
378
|
+
additionalData: {
|
|
379
|
+
isBusiness: true,
|
|
380
|
+
SessionId: sessionId,
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
socket.send(payload);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Step 5: Send to API
|
|
387
|
+
if (finalAudioUrl) {
|
|
388
|
+
apiCall(transcriptionText,newSession ,sessionId, finalAudioUrl, false);
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error('Sending audio to API/WebSocket failed:', error);
|
|
392
|
+
setMessages(prev =>
|
|
393
|
+
prev.map(msg =>
|
|
394
|
+
msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
} finally {
|
|
398
|
+
setAudioBlob(null); // Step 6: cleanup
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const { items, loading, addToCart, removeFromCart, refresh } = useCart(sessionId);
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
// replace your old handleToCart:
|
|
406
|
+
function shorten(str: string, maxLen = 20) {
|
|
407
|
+
return str.length > maxLen ? `${str.slice(0, maxLen)}…` : str;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const handleToCart = useCallback(
|
|
411
|
+
async (variantId: string, name: string, qty = 1) => {
|
|
412
|
+
await addToCart(variantId, name, qty);
|
|
413
|
+
|
|
414
|
+
// truncate the name for display
|
|
415
|
+
const shortName = shorten(name, 20);
|
|
416
|
+
|
|
417
|
+
setNotification(`✅ ${qty}× ${shortName} added to cart`);
|
|
418
|
+
},
|
|
419
|
+
[addToCart]
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const handleRemoveFromCart = useCallback(
|
|
423
|
+
async (variantId: string, qty = 1) => {
|
|
424
|
+
await removeFromCart(variantId, qty);
|
|
425
|
+
setNotification(`❌ Removed ${qty} item(s) from cart`);
|
|
426
|
+
},
|
|
427
|
+
[removeFromCart]
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
return (
|
|
434
|
+
<div className="chatbot-box">
|
|
435
|
+
<div className="chatbot-header">
|
|
436
|
+
{/* Back Button */}
|
|
437
|
+
<Button
|
|
438
|
+
type="text"
|
|
439
|
+
style={{ marginRight: 8 }}
|
|
440
|
+
onClick={() => setView('sessions')} // or use a callback like `goBack()` if needed
|
|
441
|
+
icon={<ArrowLeftOutlined />}
|
|
442
|
+
/>
|
|
443
|
+
|
|
444
|
+
{/* Avatar + Title */}
|
|
445
|
+
<Avatar
|
|
446
|
+
src={
|
|
447
|
+
chatbotLogo ||
|
|
448
|
+
'https://t4.ftcdn.net/jpg/08/90/53/75/360_F_890537572_m7EIYLzXgeTYGDNoIr7KEDOElFUTzFy0.jpg'
|
|
449
|
+
}
|
|
450
|
+
/>
|
|
451
|
+
<span className="chatbot-title">{agentName || 'Ai Assistant'}</span>
|
|
452
|
+
|
|
453
|
+
{items.length > 0 && (
|
|
454
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
455
|
+
<CartWidget
|
|
456
|
+
items={items}
|
|
457
|
+
loading={loading}
|
|
458
|
+
onRefresh={refresh}
|
|
459
|
+
onRemove={removeFromCart}
|
|
460
|
+
colorCode={colorCode}
|
|
461
|
+
sendMessage={sendMessageSuggestion}
|
|
462
|
+
/>
|
|
463
|
+
</div>
|
|
464
|
+
)}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
{/* Close Button */}
|
|
468
|
+
<CloseOutlined
|
|
469
|
+
onClick={() => setOpen(!Opened)}
|
|
470
|
+
className="chatbot-close"
|
|
471
|
+
style={{ marginLeft: 'auto' }}
|
|
472
|
+
/>
|
|
473
|
+
</div>
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
{notification && (
|
|
477
|
+
<Notification
|
|
478
|
+
message={notification}
|
|
479
|
+
onClose={() => setNotification(null)}
|
|
480
|
+
/>
|
|
481
|
+
)}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
<ChatMessages
|
|
488
|
+
messages={messages}
|
|
489
|
+
colorCode={colorCode}
|
|
490
|
+
isLoading={false}
|
|
491
|
+
handleAddToCart={handleToCart}
|
|
492
|
+
agentStatus={agentStatus}
|
|
493
|
+
sessionId={sessionId}
|
|
494
|
+
handleRemoveFromCart={handleRemoveFromCart}
|
|
495
|
+
userType={userType}
|
|
496
|
+
/>
|
|
497
|
+
|
|
498
|
+
<div className="chatbot-input-area">
|
|
499
|
+
<Suggestions sendMessageSuggestion={sendMessageSuggestion} colorCode={colorCode} suggestions={messagesSuggestions} />
|
|
500
|
+
|
|
501
|
+
<ChatInput
|
|
502
|
+
userMessage={input}
|
|
503
|
+
setUserMessage={setInput}
|
|
504
|
+
handleSendMessage={sendMessage}
|
|
505
|
+
handleSendImage={handleSendImage}
|
|
506
|
+
handleSendVoice={handleSendVoice}
|
|
507
|
+
selectedImage={selectedImage}
|
|
508
|
+
setSelectedImage={setSelectedImage}
|
|
509
|
+
audioBlob={audioBlob}
|
|
510
|
+
setAudioBlob={setAudioBlob}
|
|
511
|
+
isSending={isSending}
|
|
512
|
+
|
|
513
|
+
/>
|
|
514
|
+
|
|
515
|
+
<Powered />
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
);
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
export default Messenger;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/components/Notification.tsx
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
interface NotificationProps {
|
|
5
|
+
message: string;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
duration?: number; // milliseconds before auto-close
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Notification: React.FC<NotificationProps> = ({
|
|
11
|
+
message,
|
|
12
|
+
onClose,
|
|
13
|
+
duration = 1000,
|
|
14
|
+
}) => {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const timer = setTimeout(onClose, duration);
|
|
17
|
+
return () => clearTimeout(timer);
|
|
18
|
+
}, [onClose, duration]);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="fixed top-5 right-4 bg-gray-400 text-white px-4 py-2 rounded-lg shadow-lg z-50">
|
|
22
|
+
{message}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default Notification;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Input, Typography, Spin } from 'antd';
|
|
3
|
+
import { ArrowLeftOutlined, LoadingOutlined } from '@ant-design/icons';
|
|
4
|
+
import config from '../../../hooks/config';
|
|
5
|
+
import { getCookie } from '../../../utils/cookies';
|
|
6
|
+
|
|
7
|
+
const { Title, Text } = Typography;
|
|
8
|
+
|
|
9
|
+
type CreateSessionProps = {
|
|
10
|
+
onBack: () => void;
|
|
11
|
+
onCreate: (data: {
|
|
12
|
+
name: string;
|
|
13
|
+
email: string;
|
|
14
|
+
phone: string;
|
|
15
|
+
session_id: string;
|
|
16
|
+
messages: any[];
|
|
17
|
+
hasInfo:boolean;
|
|
18
|
+
}) => void;
|
|
19
|
+
colorCode: string;
|
|
20
|
+
visitorId: string;
|
|
21
|
+
country: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const CreateSession: React.FC<CreateSessionProps> = ({ onBack, onCreate, colorCode, visitorId, country }) => {
|
|
25
|
+
const [name, setName] = useState('');
|
|
26
|
+
const [email, setEmail] = useState('');
|
|
27
|
+
const [phone, setPhone] = useState('');
|
|
28
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
29
|
+
|
|
30
|
+
const handleSubmit = async () => {
|
|
31
|
+
const payload = {
|
|
32
|
+
visitor_id:visitorId,
|
|
33
|
+
country,
|
|
34
|
+
name,
|
|
35
|
+
email,
|
|
36
|
+
phone,
|
|
37
|
+
company_username: getCookie('company_username'),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
setIsLoading(true);
|
|
42
|
+
|
|
43
|
+
const res = await fetch(`${config.apiUrl}chatlog-create/`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify(payload),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const data = await res.json();
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if (res.ok ) {
|
|
55
|
+
|
|
56
|
+
onCreate({
|
|
57
|
+
name,
|
|
58
|
+
email,
|
|
59
|
+
phone,
|
|
60
|
+
session_id: data?.user_session_id,
|
|
61
|
+
messages: data.messages,
|
|
62
|
+
hasInfo:true
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
} else {
|
|
66
|
+
console.error('Failed to register:', data);
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Error submitting session data:', err);
|
|
70
|
+
} finally {
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div style={{ padding: '1.5rem' }}>
|
|
77
|
+
<Button
|
|
78
|
+
type="text"
|
|
79
|
+
icon={<ArrowLeftOutlined />}
|
|
80
|
+
onClick={onBack}
|
|
81
|
+
style={{ marginBottom: '1rem' }}
|
|
82
|
+
>
|
|
83
|
+
Back
|
|
84
|
+
</Button>
|
|
85
|
+
|
|
86
|
+
<Title level={4} style={{ marginBottom: '2rem' }}>
|
|
87
|
+
Let's create a session
|
|
88
|
+
</Title>
|
|
89
|
+
|
|
90
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
|
91
|
+
<div>
|
|
92
|
+
<Text strong>👋 Please, can I have your name?</Text>
|
|
93
|
+
<Input
|
|
94
|
+
size="large"
|
|
95
|
+
placeholder="John Doe"
|
|
96
|
+
value={name}
|
|
97
|
+
onChange={(e) => setName(e.target.value)}
|
|
98
|
+
style={{ marginTop: '0.5rem' }}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div>
|
|
103
|
+
<Text strong>📧 What’s your email address?</Text>
|
|
104
|
+
<Input
|
|
105
|
+
size="large"
|
|
106
|
+
type="email"
|
|
107
|
+
placeholder="you@example.com"
|
|
108
|
+
value={email}
|
|
109
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
110
|
+
style={{ marginTop: '0.5rem' }}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div>
|
|
115
|
+
<Text strong>📱 May I have your phone number?</Text>
|
|
116
|
+
<Input
|
|
117
|
+
size="large"
|
|
118
|
+
placeholder="+1 234 567 8900"
|
|
119
|
+
value={phone}
|
|
120
|
+
onChange={(e) => setPhone(e.target.value)}
|
|
121
|
+
style={{ marginTop: '0.5rem' }}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<Button
|
|
126
|
+
type="primary"
|
|
127
|
+
size="large"
|
|
128
|
+
style={{ backgroundColor: colorCode, color: 'white' }}
|
|
129
|
+
onClick={handleSubmit}
|
|
130
|
+
disabled={!name || !email || !phone}
|
|
131
|
+
>
|
|
132
|
+
{isLoading ? <Spin indicator={<LoadingOutlined style={{ fontSize: 20, color: 'white' }} spin />} /> : 'Create Session'}
|
|
133
|
+
</Button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default CreateSession;
|