shopify-chatbot-widget 0.9.8 → 1.0.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shopify-chatbot-widget",
3
3
  "private": false,
4
- "version": "0.9.8",
4
+ "version": "1.0.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -1,6 +1,6 @@
1
- import React,{useEffect, useRef} from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { Spin } from 'antd';
3
- import { LoadingOutlined ,CheckCircleOutlined,CloseCircleOutlined} from '@ant-design/icons';
3
+ import { LoadingOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
4
4
  import Typing from './Typing';
5
5
  import AssistantMessage from './AssistantMesage';
6
6
  import ChatImage from './ChatImage';
@@ -21,12 +21,12 @@ interface Message {
21
21
  content?: any;
22
22
  text?: string;
23
23
  typing?: boolean;
24
- image_url?:string;
25
- status?:string;
26
- audio_url?:string;
27
- isConnect?:boolean;
28
- calendly?:boolean;
29
- isBookings?:boolean;
24
+ image_url?: string;
25
+ status?: string;
26
+ audio_url?: string;
27
+ isConnect?: boolean;
28
+ calendly?: boolean;
29
+ isBookings?: boolean;
30
30
  }
31
31
 
32
32
  interface ChatMessagesProps {
@@ -37,28 +37,74 @@ interface ChatMessagesProps {
37
37
  handleAddToCart: (
38
38
  variantId: string,
39
39
  name: string,
40
- variantImage:string,
41
- price:string,
40
+ variantImage: string,
41
+ price: string,
42
42
  qty?: number,
43
-
44
43
  ) => Promise<void>;
45
44
 
46
45
  handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
47
- sessionId:string;
46
+ sessionId: string;
48
47
  isBusiness?: boolean;
49
48
  agentStatus?: {
50
49
  status?: string;
51
50
  user?: string;
52
51
  };
53
- userType:string;
54
- calendly:string;
52
+ userType: string;
53
+ calendly: string;
55
54
  disableCheckout: boolean;
56
- isfetchingMessage:boolean;
57
- }
58
-
55
+ isfetchingMessage: boolean;
59
56
 
57
+ messagesSuggestions: string[];
58
+ sendMessageSuggestion: (message: string) => Promise<void>;
59
+ }
60
60
 
61
+ const InlineSuggestions: React.FC<{
62
+ suggestions: string[];
63
+ colorCode: string;
64
+ onSuggestionClick: (text: string) => void;
65
+ }> = ({ suggestions, colorCode, onSuggestionClick }) => {
66
+ if (suggestions.length === 0) return null;
61
67
 
68
+ return (
69
+ <div
70
+ style={{
71
+ display: "flex",
72
+ flexWrap: "wrap",
73
+ gap: "0.5rem",
74
+ marginTop: "0.75rem",
75
+ marginBottom: "0.5rem",
76
+ paddingLeft: "0.5rem",
77
+ }}
78
+ >
79
+ {suggestions.map((text, index) => (
80
+ <button
81
+ key={index}
82
+ onClick={() => onSuggestionClick(text)}
83
+ style={{
84
+ padding: "0.5rem 1rem",
85
+ border: `1px solid ${colorCode}`,
86
+ borderRadius: "9999px",
87
+ backgroundColor: "white",
88
+ cursor: "pointer",
89
+ fontSize: "13px",
90
+ color: colorCode,
91
+ transition: "all 0.2s",
92
+ }}
93
+ onMouseEnter={(e) => {
94
+ e.currentTarget.style.backgroundColor = colorCode;
95
+ e.currentTarget.style.color = "white";
96
+ }}
97
+ onMouseLeave={(e) => {
98
+ e.currentTarget.style.backgroundColor = "white";
99
+ e.currentTarget.style.color = colorCode;
100
+ }}
101
+ >
102
+ {text}
103
+ </button>
104
+ ))}
105
+ </div>
106
+ );
107
+ };
62
108
 
63
109
  const ChatMessages: React.FC<ChatMessagesProps> = ({
64
110
  messages,
@@ -72,19 +118,29 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
72
118
  calendly,
73
119
  disableCheckout,
74
120
  isfetchingMessage,
121
+ messagesSuggestions,
122
+ sendMessageSuggestion,
75
123
  }) => {
76
124
  const bottomRef = useRef<HTMLDivElement>(null);
77
125
 
78
126
  useEffect(() => {
79
127
  if (!bottomRef.current) return;
80
-
128
+
81
129
  const timeout = setTimeout(() => {
82
130
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
83
131
  }, 150);
84
-
132
+
85
133
  return () => clearTimeout(timeout);
86
134
  }, [messages]);
87
135
 
136
+ const getRandomSuggestions = (count: number = 2) => {
137
+ if (messagesSuggestions.length <= count) return messagesSuggestions;
138
+
139
+ const shuffled = [...messagesSuggestions].sort(() => 0.5 - Math.random());
140
+ return shuffled.slice(0, count);
141
+ };
142
+
143
+
88
144
  if (isfetchingMessage || isLoading || !messages) {
89
145
  return (
90
146
  <div
@@ -97,9 +153,9 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
97
153
  gap: "8px",
98
154
  }}
99
155
  >
100
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
101
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
102
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
156
+ <div style={{ backgroundColor: colorCode }} className="box-loader"></div>
157
+ <div style={{ backgroundColor: colorCode }} className="box-loader"></div>
158
+ <div style={{ backgroundColor: colorCode }} className="box-loader"></div>
103
159
  </div>
104
160
  );
105
161
  }
@@ -108,7 +164,11 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
108
164
  <div className="chatbot-messages space-y-4">
109
165
  {messages.map((msg, i) => {
110
166
  const isFirstMessage = i === 0 && messages.length === 1;
111
-
167
+ // CHECK if this is the last assistant message
168
+ const isLastAssistantMessage =
169
+ msg.role === 'assistant' &&
170
+ i === messages.length - 1;
171
+
112
172
  if (msg.typing) {
113
173
  return (
114
174
  <div key={i} className="chat-msg typing">
@@ -139,10 +199,41 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
139
199
  if (msg.role === 'assistant') {
140
200
  if (Array.isArray(msg.content)) {
141
201
  return (
142
- <div
143
- key={i}
202
+ <div key={i}> {/* WRAP in div to add suggestions after */}
203
+ <div
204
+ className={`chat-msg assistant ${isFirstMessage ? 'first-message' : ''}`}
205
+ style={{ backgroundColor: 'transparent', padding: 0 }}
206
+ >
207
+ {msg.image_url && (
208
+ <img
209
+ src={msg.image_url}
210
+ alt="Chat image"
211
+ style={{ width: 200 }}
212
+ className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
213
+ />
214
+ )}
215
+ <ChatImage message={msg.content} />
216
+ <div className="flex justify-start">
217
+ <ChatImageTextAssist message={msg.content} />
218
+ </div>
219
+ </div>
220
+
221
+ {/* ADD suggestions here */}
222
+ {isLastAssistantMessage && messagesSuggestions.length > 0 && (
223
+ <InlineSuggestions
224
+ suggestions={getRandomSuggestions(2)}
225
+ colorCode={colorCode}
226
+ onSuggestionClick={sendMessageSuggestion}
227
+ />
228
+ )}
229
+ </div>
230
+ );
231
+ }
232
+
233
+ return (
234
+ <div key={i}> {/* WRAP in div to add suggestions after */}
235
+ <div
144
236
  className={`chat-msg assistant ${isFirstMessage ? 'first-message' : ''}`}
145
- style={{ backgroundColor: 'transparent', padding: 0 }}
146
237
  >
147
238
  {msg.image_url && (
148
239
  <img
@@ -152,49 +243,38 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
152
243
  className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
153
244
  />
154
245
  )}
155
- <ChatImage message={msg.content} />
156
- <div className="flex justify-start">
157
- <ChatImageTextAssist message={msg.content} />
158
- </div>
246
+
247
+ {userType === "shopify" ? (
248
+ <AssistantMessage
249
+ message={msg.content || msg.text || ""}
250
+ colorCode={colorCode}
251
+ disableCheckout={disableCheckout}
252
+ handleAddToCart={handleAddToCart}
253
+ handleRemoveFromCart={handleRemoveFromCart}
254
+ />
255
+ ) : userType === "salla" ? (
256
+ <SallaAsssiatnt
257
+ message={msg.content || msg.text || ""}
258
+ colorCode={colorCode}
259
+ handleAddToCart={handleAddToCart}
260
+ handleRemoveFromCart={handleRemoveFromCart}
261
+ />
262
+ ) : (
263
+ <ZidAssistantChatMessage
264
+ message={msg.content || msg.text || ""}
265
+ colorCode={colorCode}
266
+ handleAddToCart={handleAddToCart}
267
+ handleRemoveFromCart={handleRemoveFromCart}
268
+ />
269
+ )}
159
270
  </div>
160
- );
161
- }
162
271
 
163
- return (
164
- <div
165
- key={i}
166
- className={`chat-msg assistant ${isFirstMessage ? 'first-message' : ''}`}
167
- >
168
- {msg.image_url && (
169
- <img
170
- src={msg.image_url}
171
- alt="Chat image"
172
- style={{ width: 200 }}
173
- className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
174
- />
175
- )}
176
-
177
- {userType === "shopify" ? (
178
- <AssistantMessage
179
- message={msg.content || msg.text || ""}
272
+ {/* ADD suggestions here - MOVED OUTSIDE the chat-msg div */}
273
+ {isLastAssistantMessage && messagesSuggestions.length > 0 && (
274
+ <InlineSuggestions
275
+ suggestions={getRandomSuggestions(2)}
180
276
  colorCode={colorCode}
181
- disableCheckout={disableCheckout}
182
- handleAddToCart={handleAddToCart}
183
- handleRemoveFromCart={handleRemoveFromCart}
184
- />
185
- ) : userType === "salla" ? (
186
- <SallaAsssiatnt
187
- message={msg.content || msg.text || ""}
188
- colorCode={colorCode}
189
- handleAddToCart={handleAddToCart}
190
- handleRemoveFromCart={handleRemoveFromCart}
191
- />
192
- ) : (
193
- <ZidAssistantChatMessage
194
- message={msg.content || msg.text || ""}
195
- colorCode={colorCode}
196
- handleAddToCart={handleAddToCart}
197
- handleRemoveFromCart={handleRemoveFromCart}
277
+ onSuggestionClick={sendMessageSuggestion}
198
278
  />
199
279
  )}
200
280
  </div>
@@ -234,11 +314,10 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
234
314
  src={msg.image_url}
235
315
  alt="Chat image"
236
316
  style={{ width: 200 }}
237
- className={`rounded-md mt-2 max-w-xs ${
238
- msg.content === 'user'
317
+ className={`rounded-md mt-2 max-w-xs ${msg.content === 'user'
239
318
  ? 'border border-gray-300 shadow-sm'
240
319
  : 'border border-green-400 shadow-sm'
241
- }`}
320
+ }`}
242
321
  />
243
322
  <div className="mt-1 mr-2 flex items-end justify-end">
244
323
  {msg.status === 'uploading' ? (
@@ -275,8 +354,8 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
275
354
 
276
355
  if (Array.isArray(msg.content)) {
277
356
  return (
278
- <div key={i} className='chat-msg user' style={{backgroundColor:'transparent', padding:0}}>
279
- <div style={{backgroundColor:'transparent', padding:0}}>
357
+ <div key={i} className='chat-msg user' style={{ backgroundColor: 'transparent', padding: 0 }}>
358
+ <div style={{ backgroundColor: 'transparent', padding: 0 }}>
280
359
  <ChatImage message={msg.content} />
281
360
  </div>
282
361
  <div className='flex justify-end'>
@@ -296,11 +375,11 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
296
375
  : {}
297
376
  }
298
377
  >
299
- <AddToCartConfirmation message={msg.content} colorCode={colorCode}/>
378
+ <AddToCartConfirmation message={msg.content} colorCode={colorCode} />
300
379
  </div>
301
380
  );
302
381
  })}
303
-
382
+
304
383
  <AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
305
384
  <div ref={bottomRef} />
306
385
  </div>
@@ -5,7 +5,6 @@ import '../../Chatbot.css';
5
5
  import { useChatbotMessage } from '../../../hooks/useSessionMessages';
6
6
  import axios from 'axios';
7
7
  import ChatInput from '../../components/ChatInput';
8
- // import Suggestions from '../../components/Suggestions';
9
8
  import Powered from '../../components/Powered';
10
9
  import ChatMessages from '../../components/Messages/ChatMessages';
11
10
  import config from '../../../hooks/config';
@@ -52,6 +51,8 @@ const Messenger: React.FC<MessengerProps> = ({
52
51
  newSession,
53
52
  userType,
54
53
  setNewSession,
54
+ messagesSuggestions,
55
+ setMessagesSuggestions,
55
56
  disableForm,
56
57
  sessionId,
57
58
  setOpen,
@@ -165,34 +166,34 @@ const socket = useChatSocket(sessionId, handleSocketData);
165
166
  }
166
167
  };
167
168
 
168
- // const sendMessageSuggestion = async (message: string): Promise<void> => {
169
- // if (!message.trim()) return;
169
+ const sendMessageSuggestion = async (message: string): Promise<void> => {
170
+ if (!message.trim()) return;
170
171
 
171
- // setIsSending(true);
172
+ setIsSending(true);
172
173
 
173
- // // Remove only the clicked suggestion
174
- // setMessagesSuggestions(prev => prev.filter(suggestion => suggestion !== message));
174
+ // Remove only the clicked suggestion
175
+ setMessagesSuggestions(prev => prev.filter(suggestion => suggestion !== message));
175
176
 
176
- // const userMsg: ChatMessage = { role: 'user', content: message };
177
- // setMessages(prev => [...prev, userMsg]);
177
+ const userMsg: ChatMessage = { role: 'user', content: message };
178
+ setMessages(prev => [...prev, userMsg]);
178
179
 
179
- // if (socket && socket.readyState === WebSocket.OPEN) {
180
- // const additionalData = { isBusiness: true, SessionId: sessionId };
181
- // const messagePayload = JSON.stringify({
182
- // message,
183
- // additionalData,
184
- // });
185
- // socket.send(messagePayload);
186
- // }
180
+ if (socket && socket.readyState === WebSocket.OPEN) {
181
+ const additionalData = { isBusiness: true, SessionId: sessionId };
182
+ const messagePayload = JSON.stringify({
183
+ message,
184
+ additionalData,
185
+ });
186
+ socket.send(messagePayload);
187
+ }
187
188
 
188
- // try {
189
- // await apiCall(message, newSession, sessionId);
190
- // } catch (error) {
191
- // console.error("Error sending suggestion:", error);
192
- // } finally {
193
- // setIsSending(false);
194
- // }
195
- // };
189
+ try {
190
+ await apiCall(message, newSession, sessionId);
191
+ } catch (error) {
192
+ console.error("Error sending suggestion:", error);
193
+ } finally {
194
+ setIsSending(false);
195
+ }
196
+ };
196
197
 
197
198
 
198
199
 
@@ -535,20 +536,21 @@ const socket = useChatSocket(sessionId, handleSocketData);
535
536
 
536
537
 
537
538
 
538
- <ChatMessages
539
- messages={messages}
540
- colorCode={colorCode}
541
- isLoading={false}
542
- handleAddToCart={handleToCart}
543
- agentStatus={agentStatus}
544
- sessionId={sessionId}
545
- handleRemoveFromCart={handleRemoveFromCart}
546
- userType={userType}
547
- calendly={calendly}
548
- disableCheckout={disbleCheckout}
549
- isfetchingMessage={isFetchingMessages}
550
- />
551
-
539
+ <ChatMessages
540
+ messages={messages}
541
+ colorCode={colorCode}
542
+ isLoading={false}
543
+ handleAddToCart={handleToCart}
544
+ agentStatus={agentStatus}
545
+ sessionId={sessionId}
546
+ handleRemoveFromCart={handleRemoveFromCart}
547
+ userType={userType}
548
+ calendly={calendly}
549
+ disableCheckout={disbleCheckout}
550
+ isfetchingMessage={isFetchingMessages}
551
+ messagesSuggestions={messagesSuggestions}
552
+ sendMessageSuggestion={sendMessageSuggestion}
553
+ />
552
554
 
553
555
 
554
556
 
@@ -1,11 +1,11 @@
1
1
 
2
2
 
3
3
  const config = {
4
- apiUrl:'https://jawebcrm.onrender.com/api/',
5
- websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
4
+ // apiUrl:'https://jawebcrm.onrender.com/api/',
5
+ // websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
6
6
 
7
- // apiUrl:'http://localhost:8000/api/',
8
- // websocketUrl:'ws://localhost:8000/ws',
7
+ apiUrl:'http://localhost:8000/api/',
8
+ websocketUrl:'ws://localhost:8000/ws',
9
9
 
10
10
  };
11
11
  export default config;
@@ -1,35 +0,0 @@
1
- /* Suggestions.css */
2
- .suggestions-wrapper {
3
- overflow-x: auto;
4
- overflow-y: hidden;
5
- white-space: nowrap;
6
- padding-top: 4px;
7
- padding-bottom: 8px;
8
- width: 100%; /* ensures it respects screen boundaries */
9
- box-sizing: border-box;
10
- margin-left: 4%;
11
- margin-top: 4px;
12
- margin-bottom: 4px;
13
-
14
- }
15
-
16
- .suggestions-scroll {
17
- display: inline-flex; /* horizontal scroll with inline behavior */
18
- gap: 0.5rem;
19
- }
20
-
21
- .suggestion-button {
22
- white-space: nowrap;
23
- padding: 0.5rem 1rem;
24
- border: 1px solid #ccc;
25
- border-radius: 9999px;
26
- background-color: white;
27
- cursor: pointer;
28
- font-size: 0.9rem;
29
- flex-shrink: 0; /* prevents shrinking inside flex */
30
- }
31
-
32
- .suggestion-button:hover {
33
- background-color: #f3f3f3;
34
- }
35
-
@@ -1,57 +0,0 @@
1
- interface SuggestionsProps {
2
- suggestions: string[];
3
- colorCode: string;
4
- sendMessageSuggestion: (message: string) => Promise<void>;
5
- }
6
-
7
- export default function Suggestions({ suggestions, colorCode, sendMessageSuggestion }: SuggestionsProps) {
8
- return (
9
- <div
10
- style={{
11
- overflowX: "auto",
12
- overflowY: "hidden",
13
- whiteSpace: "nowrap",
14
- paddingTop: "4px",
15
- paddingBottom: "8px",
16
- width: "100%",
17
- boxSizing: "border-box",
18
- marginLeft: "4%",
19
- marginTop: "4px",
20
- marginBottom: "4px",
21
- }}
22
- >
23
- <div
24
- style={{
25
- display: "inline-flex",
26
- gap: "0.5rem",
27
- }}
28
- >
29
- {suggestions?.map((text, index) => (
30
- <button
31
- key={index}
32
- onClick={() => sendMessageSuggestion(text)}
33
- style={{
34
- whiteSpace: "nowrap",
35
- padding: "0.5rem 1rem",
36
- border: `1px solid ${colorCode}`,
37
- borderRadius: "9999px",
38
- backgroundColor: "white",
39
- cursor: "pointer",
40
- fontSize: "14px",
41
- color: colorCode,
42
- flexShrink: 0,
43
- }}
44
- onMouseEnter={(e) => {
45
- (e.currentTarget.style.backgroundColor = "#f3f3f3");
46
- }}
47
- onMouseLeave={(e) => {
48
- (e.currentTarget.style.backgroundColor = "white");
49
- }}
50
- >
51
- {text}
52
- </button>
53
- ))}
54
- </div>
55
- </div>
56
- );
57
- }