shopify-chatbot-widget 0.7.9 → 0.8.2

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.
Files changed (58) hide show
  1. package/dist/jaweb-chatbot.css +1 -1
  2. package/dist/jaweb-chatbot.iife.js +3 -3
  3. package/package.json +1 -1
  4. package/src/App.css +42 -0
  5. package/src/App.tsx +13 -0
  6. package/src/Chatbot/Chatbot.css +270 -0
  7. package/src/Chatbot/Chatbot.tsx +214 -0
  8. package/src/Chatbot/ChatbotTransition.css +19 -0
  9. package/src/Chatbot/OLDCHAT.tsx +259 -0
  10. package/src/Chatbot/components/ChatInput.css +127 -0
  11. package/src/Chatbot/components/ChatInput.tsx +354 -0
  12. package/src/Chatbot/components/Messages/Addcart.tsx +70 -0
  13. package/src/Chatbot/components/Messages/AgentStatus.tsx +37 -0
  14. package/src/Chatbot/components/Messages/AssistantMesage.tsx +206 -0
  15. package/src/Chatbot/components/Messages/AudioMessage.tsx +123 -0
  16. package/src/Chatbot/components/Messages/CalendlyEmbeds.tsx +66 -0
  17. package/src/Chatbot/components/Messages/CardSwiper.tsx +194 -0
  18. package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +264 -0
  19. package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +208 -0
  20. package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +208 -0
  21. package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +245 -0
  22. package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +111 -0
  23. package/src/Chatbot/components/Messages/CartWidget.tsx +151 -0
  24. package/src/Chatbot/components/Messages/ChatImage.tsx +46 -0
  25. package/src/Chatbot/components/Messages/ChatImageText.tsx +74 -0
  26. package/src/Chatbot/components/Messages/ChatImageTextAssis.tsx +72 -0
  27. package/src/Chatbot/components/Messages/ChatMessages.css +31 -0
  28. package/src/Chatbot/components/Messages/ChatMessages.tsx +334 -0
  29. package/src/Chatbot/components/Messages/ProductCard.css +149 -0
  30. package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +196 -0
  31. package/src/Chatbot/components/Messages/Support.tsx +177 -0
  32. package/src/Chatbot/components/Messages/Typing.css +31 -0
  33. package/src/Chatbot/components/Messages/Typing.tsx +19 -0
  34. package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +196 -0
  35. package/src/Chatbot/components/Messages/cartwidget.css +123 -0
  36. package/src/Chatbot/components/Messenger/Messenger.tsx +531 -0
  37. package/src/Chatbot/components/Notification.tsx +28 -0
  38. package/src/Chatbot/components/Powered.css +29 -0
  39. package/src/Chatbot/components/Powered.tsx +12 -0
  40. package/src/Chatbot/components/Sessions/CreateSession.tsx +201 -0
  41. package/src/Chatbot/components/Sessions/RenderList.tsx +212 -0
  42. package/src/Chatbot/components/Suggestions.css +35 -0
  43. package/src/Chatbot/components/Suggestions.tsx +57 -0
  44. package/src/Chatbot/notification.css +17 -0
  45. package/src/assets/react.svg +1 -0
  46. package/src/chatbot-widget/index.tsx +14 -0
  47. package/src/hooks/config.tsx +13 -0
  48. package/src/hooks/useCart.tsx +107 -0
  49. package/src/hooks/useChatbotAPI.tsx +167 -0
  50. package/src/hooks/useChatbotData.tsx +132 -0
  51. package/src/hooks/useSessionMessages.tsx +50 -0
  52. package/src/hooks/useWebsocke.tsx +45 -0
  53. package/src/i18n.tsx +30 -0
  54. package/src/index.css +2 -0
  55. package/src/main.tsx +10 -0
  56. package/src/types/chatbot.ts +40 -0
  57. package/src/utils/cookies.tsx +14 -0
  58. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,531 @@
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
+ Opened: boolean;
33
+ calendly:string;
34
+ disbleCheckout: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
+ calendly,
61
+ disbleCheckout
62
+ }) => {
63
+ const [input, setInput] = useState('');
64
+ const [selectedImage, setSelectedImage] = useState<File | null>(null);
65
+ const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
66
+ const [isSending, setIsSending] = useState(false);
67
+
68
+
69
+
70
+
71
+ const [, setNewUser] = useState(() => {
72
+ const cookieValue = getCookie('new_user');
73
+ return cookieValue === 'true';
74
+ });
75
+
76
+
77
+ const [isChatlogCreated, setIsChatlogCreated] = useState(false);
78
+ const [, setChatLogWaiting] = useState(false);
79
+ const [createNew, setCreateNew] = useState(false);
80
+ const [, setSessionCreated] = useState(false);
81
+
82
+
83
+ const { messages, setMessages,isFetchingMessages } = useChatbotMessage(sessionId);
84
+ ;
85
+ const [agentStatus, setAgentStatus] = useState<{ status?: string; typing?: boolean; user?: string }>({});
86
+ const [notification, setNotification] = useState<string | null>(null);
87
+
88
+
89
+
90
+
91
+
92
+ const handleSocketData = useCallback(
93
+ (data: any) => {
94
+ if (data.status) {
95
+ setAgentStatus({
96
+ status: data.status,
97
+ user: data.user || 'Support',
98
+ });
99
+ return;
100
+ }
101
+ const { additionalData = {}, message, image_url } = data;
102
+ if (!additionalData.isBusiness && additionalData.sender !== 'AI') {
103
+ setMessages((prev) => [
104
+ ...prev,
105
+ {
106
+ role: 'assistant',
107
+ content: message,
108
+ isBusiness: false,
109
+ sender: additionalData.sender,
110
+ image_url,
111
+ } as ChatMessage,
112
+ ]);
113
+ }
114
+ },
115
+ [setMessages]
116
+ );
117
+
118
+ const socket = useChatSocket(sessionId, handleSocketData);
119
+
120
+
121
+
122
+ const { apiCall } = useChatbotAPI({
123
+ disableChatbot: false,
124
+ disableChatbotGlobally: false,
125
+ isChatlogCreated,
126
+ createNew,
127
+ disableForm: disableForm,
128
+ calendlyLink: calendly,
129
+ socket:socket,
130
+ newSession:newSession,
131
+ setMessages,
132
+ setIsChatlogCreated,
133
+ setChatLogWaiting,
134
+ setNewUser,
135
+ setCreateNew,
136
+ setSessionCreated,
137
+ setNewSession:setNewSession
138
+ });
139
+
140
+
141
+
142
+ const sendMessage = async () => {
143
+ if (!input.trim()) return;
144
+
145
+ let message=input
146
+ setIsSending(true); // 🔒 Disable input
147
+ const userMsg: ChatMessage = { role: 'user', content: message };
148
+ setMessages(prev => [...prev, userMsg]);
149
+
150
+ if (socket && socket.readyState === WebSocket.OPEN) {
151
+ const additionalData = { isBusiness: true, SessionId: sessionId };
152
+ const messagePayload = JSON.stringify({
153
+ message: message,
154
+ additionalData: additionalData,
155
+ });
156
+ socket.send(messagePayload);
157
+ }
158
+
159
+ try {
160
+ await apiCall(input, newSession, sessionId);
161
+ } catch (err) {
162
+ console.error(err);
163
+ } finally {
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
+ setIsSending(false); // 🔓 Re-enable input
191
+ }
192
+ };
193
+
194
+
195
+
196
+ async function handleSendImage (image: File, userMessage:string) {
197
+ const tempMessageId = Date.now();
198
+ const tempImageUrl = URL.createObjectURL(image);
199
+
200
+
201
+
202
+ if (!image) return;
203
+
204
+
205
+
206
+
207
+
208
+ const tempJson: ChatMessage = {
209
+ id: tempMessageId,
210
+ role: 'user', // ✅ satisfies union
211
+ content: userMessage || '',
212
+ image_url: tempImageUrl,
213
+ isBusiness: true,
214
+ status: 'uploading',
215
+
216
+
217
+ };
218
+
219
+
220
+ setMessages((prevMessages) => {
221
+ const updatedMessages = [...prevMessages, tempJson];
222
+ return updatedMessages;
223
+ });
224
+
225
+
226
+ let finalImageUrl: string | null = null;
227
+
228
+ if (image) {
229
+
230
+ try {
231
+ const formData = new FormData();
232
+ formData.append("image", image);
233
+
234
+ const uploadResponse = await axios.post<UploadImageResponse>(
235
+ `${config.apiUrl}upload-image/`,
236
+ formData
237
+ );
238
+
239
+
240
+ if (uploadResponse.status === 201 && uploadResponse.data?.image_url) {
241
+ finalImageUrl = uploadResponse.data.image_url;
242
+ setMessages((prevMessages) => {
243
+ const updatedMessages = prevMessages.map((msg) =>
244
+ msg.id === tempMessageId && finalImageUrl
245
+ ? { ...msg, image_url: finalImageUrl } // ✅ only if finalImageUrl is not null
246
+ : msg
247
+ );
248
+ return updatedMessages;
249
+ });
250
+ }
251
+ } catch (error) {
252
+ console.error("Error uploading image:", error);
253
+ setMessages((messageList) =>
254
+ messageList.map((msg) =>
255
+ msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
256
+ )
257
+ );
258
+ return;
259
+ }
260
+ }
261
+
262
+ try {
263
+
264
+
265
+ if (socket && socket.readyState === WebSocket.OPEN) {
266
+ const additionalData = { isBusiness: true, SessionId: sessionId };
267
+ const messagePayload = JSON.stringify({
268
+ image_url: finalImageUrl || null,
269
+ message: input.trim()||"",
270
+ additionalData: additionalData,
271
+ });
272
+
273
+ socket.send(messagePayload);
274
+
275
+ }
276
+
277
+
278
+ if (finalImageUrl) {
279
+ setMessages((messageList) =>
280
+ messageList.map((msg) =>
281
+ msg.id === tempMessageId ? { ...msg, status: 'delivered' } : msg
282
+ )
283
+ );
284
+
285
+ apiCall(userMessage.trim() || '',newSession,sessionId,finalImageUrl,true)
286
+ }
287
+
288
+
289
+
290
+
291
+ } catch (error) {
292
+ console.error('Error sending Image message:', error);
293
+
294
+ setMessages((messageList) =>
295
+ messageList.map((msg) =>
296
+ msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
297
+ )
298
+ );
299
+ } finally {
300
+ setSelectedImage(null);
301
+ }
302
+ };
303
+
304
+
305
+
306
+
307
+ const handleSendVoice = async (audio: Blob) => {
308
+ if (!audio) return;
309
+
310
+ const tempAudioUrl = URL.createObjectURL(audio);
311
+ const tempMessageId = Date.now();
312
+
313
+ // Step 1: Add temporary audio message
314
+ const tempMessage: ChatMessage = {
315
+ id: tempMessageId,
316
+ role: 'user',
317
+ content: '',
318
+ audio_url: tempAudioUrl,
319
+ isBusiness: true,
320
+ status: 'uploading',
321
+ };
322
+
323
+ setMessages(prev => [...prev, tempMessage]);
324
+
325
+ let finalAudioUrl: string | undefined = undefined;
326
+ let transcriptionText: string = '';
327
+
328
+ try {
329
+ // Step 2: Upload audio to backend
330
+ const formData = new FormData();
331
+ formData.append('audio', audio);
332
+
333
+ const uploadResponse = await axios.post<UploadAudioResponse>(
334
+ `${config.apiUrl}upload-audio/`,
335
+ formData
336
+ );
337
+
338
+
339
+
340
+
341
+
342
+
343
+ if (uploadResponse.status === 201) {
344
+ finalAudioUrl = uploadResponse.data.audio_url;
345
+ transcriptionText = uploadResponse.data.transcription_text || '[Audio]';
346
+
347
+ // Step 3: Replace temp audio URL with final URL
348
+ setMessages(prev =>
349
+ prev.map(msg =>
350
+ msg.id === tempMessageId
351
+ ? {
352
+ ...msg,
353
+ audio_url: finalAudioUrl,
354
+ content: transcriptionText,
355
+ status: 'delivered',
356
+ }
357
+ : msg
358
+ )
359
+ );
360
+ }
361
+ } catch (error) {
362
+ console.error('Audio upload failed:', error);
363
+ setMessages(prev =>
364
+ prev.map(msg =>
365
+ msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
366
+ )
367
+ );
368
+ return;
369
+ }
370
+
371
+ try {
372
+ // Step 4: Send to WebSocket
373
+ if (socket && socket.readyState === WebSocket.OPEN) {
374
+ const payload = JSON.stringify({
375
+ audio_url: finalAudioUrl,
376
+ message: transcriptionText,
377
+ additionalData: {
378
+ isBusiness: true,
379
+ SessionId: sessionId,
380
+ },
381
+ });
382
+ socket.send(payload);
383
+ }
384
+
385
+ // Step 5: Send to API
386
+ if (finalAudioUrl) {
387
+ apiCall(transcriptionText,newSession ,sessionId, finalAudioUrl, false);
388
+ }
389
+ } catch (error) {
390
+ console.error('Sending audio to API/WebSocket failed:', error);
391
+ setMessages(prev =>
392
+ prev.map(msg =>
393
+ msg.id === tempMessageId ? { ...msg, status: 'failed' } : msg
394
+ )
395
+ );
396
+ } finally {
397
+ setAudioBlob(null); // Step 6: cleanup
398
+ }
399
+ };
400
+
401
+ const { items, loading, addToCart, removeFromCart, refresh } = useCart(sessionId);
402
+
403
+
404
+ // replace your old handleToCart:
405
+ function shorten(str: string, maxLen = 20) {
406
+ return str.length > maxLen ? `${str.slice(0, maxLen)}…` : str;
407
+ }
408
+
409
+ const handleToCart = useCallback(
410
+ async (variantId: string, name: string, qty = 1) => {
411
+ try {
412
+ await addToCart(variantId, name, qty);
413
+
414
+ const shortName = shorten(name, 20);
415
+ setNotification(`✅ ${qty}× ${shortName} added to cart`);
416
+ } catch (err: any) {
417
+ setNotification(`❌ ${err.message || "Failed to add item"}`);
418
+ }
419
+ },
420
+ [addToCart]
421
+ );
422
+
423
+ const handleRemoveFromCart = useCallback(
424
+ async (variantId: string, qty = 1) => {
425
+ await removeFromCart(variantId, qty);
426
+ setNotification(`❌ Removed ${qty} item(s) from cart`);
427
+ },
428
+ [removeFromCart]
429
+ );
430
+
431
+
432
+
433
+
434
+ return (
435
+ <div className="chatbot-box">
436
+ <div className="chatbot-header">
437
+ {/* Back Button */}
438
+ <Button
439
+ type="text"
440
+ style={{ marginRight: 8 }}
441
+ onClick={() => setView('sessions')} // or use a callback like `goBack()` if needed
442
+ icon={<ArrowLeftOutlined />}
443
+ />
444
+
445
+ {/* Avatar + Title */}
446
+ <Avatar
447
+ src={
448
+ chatbotLogo ||
449
+ 'https://t4.ftcdn.net/jpg/08/90/53/75/360_F_890537572_m7EIYLzXgeTYGDNoIr7KEDOElFUTzFy0.jpg'
450
+ }
451
+ />
452
+ <span className="chatbot-title">{agentName || 'Ai Assistant'}</span>
453
+
454
+ {items.length > 0 && (
455
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
456
+ <CartWidget
457
+ items={items}
458
+ loading={loading}
459
+ sessionId={sessionId}
460
+ onRefresh={refresh}
461
+ onRemove={removeFromCart}
462
+ colorCode={colorCode}
463
+ sendMessage={sendMessageSuggestion}
464
+ />
465
+ </div>
466
+ )}
467
+
468
+
469
+ {/* Close Button */}
470
+ <CloseOutlined
471
+ onClick={() => setOpen(!Opened)}
472
+ className="chatbot-close"
473
+ style={{ marginLeft: 'auto' }}
474
+ />
475
+ </div>
476
+
477
+
478
+ {notification && (
479
+ <Notification
480
+ message={notification}
481
+ onClose={() => setNotification(null)}
482
+ />
483
+ )}
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+ <ChatMessages
492
+ messages={messages}
493
+ colorCode={colorCode}
494
+ isLoading={false}
495
+ handleAddToCart={handleToCart}
496
+ agentStatus={agentStatus}
497
+ sessionId={sessionId}
498
+ handleRemoveFromCart={handleRemoveFromCart}
499
+ userType={userType}
500
+ calendly={calendly}
501
+ disableCheckout={disbleCheckout}
502
+ isfetchingMessage={isFetchingMessages}
503
+ />
504
+
505
+
506
+
507
+
508
+ <div className="chatbot-input-area">
509
+ <Suggestions sendMessageSuggestion={sendMessageSuggestion} colorCode={colorCode} suggestions={messagesSuggestions} />
510
+
511
+ <ChatInput
512
+ userMessage={input}
513
+ setUserMessage={setInput}
514
+ handleSendMessage={sendMessage}
515
+ handleSendImage={handleSendImage}
516
+ handleSendVoice={handleSendVoice}
517
+ selectedImage={selectedImage}
518
+ setSelectedImage={setSelectedImage}
519
+ audioBlob={audioBlob}
520
+ setAudioBlob={setAudioBlob}
521
+ isSending={isSending}
522
+
523
+ />
524
+
525
+ <Powered />
526
+ </div>
527
+ </div>
528
+ );
529
+ };
530
+
531
+ export default Messenger;
@@ -0,0 +1,28 @@
1
+ // src/components/Notification.tsx
2
+ import React, { useEffect } from 'react';
3
+ import '../notification.css'; // Import the CSS
4
+
5
+ interface NotificationProps {
6
+ message: string;
7
+ onClose: () => void;
8
+ duration?: number;
9
+ }
10
+
11
+ const Notification: React.FC<NotificationProps> = ({
12
+ message,
13
+ onClose,
14
+ duration = 1000,
15
+ }) => {
16
+ useEffect(() => {
17
+ const timer = setTimeout(onClose, duration);
18
+ return () => clearTimeout(timer);
19
+ }, [onClose, duration]);
20
+
21
+ return (
22
+ <div className="jawebcss-notification">
23
+ {message}
24
+ </div>
25
+ );
26
+ };
27
+
28
+ export default Notification;
@@ -0,0 +1,29 @@
1
+ .jawebcss-powered-container {
2
+ display: inline-flex;
3
+ margin-top: 10px;
4
+ align-items: center;
5
+ gap: 6px;
6
+ font-family: Arial, sans-serif;
7
+ font-size: 14px;
8
+ color: #555;
9
+ padding: 4px 8px;
10
+ border-radius: 6px;
11
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
12
+ }
13
+
14
+ .jawebcss-powered-label {
15
+ font-weight: 400;
16
+ }
17
+
18
+ .jawebcss-powered-link {
19
+ color: #7f28f8;
20
+ font-weight: 600;
21
+ text-decoration: none;
22
+ transition: color 0.2s ease;
23
+ }
24
+
25
+ .jawebcss-powered-link:hover {
26
+ color: #5e1dc3;
27
+ text-decoration: underline;
28
+ }
29
+
@@ -0,0 +1,12 @@
1
+ import './Powered.css';
2
+
3
+ export default function Powered() {
4
+ return (
5
+ <div className="jawebcss-powered-container">
6
+ <span className="jawebcss-powered-label">Powered by</span>
7
+ <a href="https://jaweb.me/" target="_blank" className="jawebcss-powered-link" rel="noopener noreferrer">
8
+ Jaweb
9
+ </a>
10
+ </div>
11
+ );
12
+ }