shopify-chatbot-widget 0.9.7 → 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.
@@ -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,24 +118,27 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
72
118
  calendly,
73
119
  disableCheckout,
74
120
  isfetchingMessage,
75
-
121
+ messagesSuggestions,
122
+ sendMessageSuggestion,
76
123
  }) => {
77
-
78
-
79
-
80
-
81
124
  const bottomRef = useRef<HTMLDivElement>(null);
82
125
 
83
126
  useEffect(() => {
84
127
  if (!bottomRef.current) return;
85
-
128
+
86
129
  const timeout = setTimeout(() => {
87
130
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
88
- }, 150); // Slight delay ensures DOM settles in Shopify
89
-
131
+ }, 150);
132
+
90
133
  return () => clearTimeout(timeout);
91
134
  }, [messages]);
92
-
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
+ };
93
142
 
94
143
 
95
144
  if (isfetchingMessage || isLoading || !messages) {
@@ -104,32 +153,35 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
104
153
  gap: "8px",
105
154
  }}
106
155
  >
107
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
108
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
109
- <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>
110
159
  </div>
111
160
  );
112
161
  }
113
-
114
162
 
115
163
  return (
116
164
  <div className="chatbot-messages space-y-4">
117
- {messages.map((msg, i) => {
118
- if (msg.typing) {
119
- return (
120
- <div key={i} className="chat-msg typing">
121
- <Typing color={colorCode} />
122
- </div>
123
- );
124
- }
165
+ {messages.map((msg, i) => {
166
+ const isFirstMessage = i === 0 && messages.length === 1;
167
+ // CHECK if this is the last assistant message
168
+ const isLastAssistantMessage =
169
+ msg.role === 'assistant' &&
170
+ i === messages.length - 1;
171
+
172
+ if (msg.typing) {
173
+ return (
174
+ <div key={i} className="chat-msg typing">
175
+ <Typing color={colorCode} />
176
+ </div>
177
+ );
178
+ }
125
179
 
126
- if (msg.isConnect){
127
- return(
128
- <SupportConnect sessionId={sessionId}/>
129
- )
130
- }
180
+ if (msg.isConnect) {
181
+ return <SupportConnect key={i} sessionId={sessionId} />;
182
+ }
131
183
 
132
- if (msg.isBookings){
184
+ if (msg.isBookings) {
133
185
  return (
134
186
  <div
135
187
  key={i}
@@ -142,200 +194,196 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
142
194
  <CalendlyEmbeds calendlyLink={calendly} />
143
195
  </div>
144
196
  );
145
- }
146
-
197
+ }
147
198
 
199
+ if (msg.role === 'assistant') {
200
+ if (Array.isArray(msg.content)) {
201
+ return (
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
+ }
148
232
 
149
- if (msg.role === 'assistant') {
150
- // Handle array content separately
151
- if (Array.isArray(msg.content)) {
152
233
  return (
153
- <div key={i} className="chat-msg assistant" style={{ backgroundColor: 'transparent', padding: 0 }}>
154
- {msg.image_url && (
155
- <img
156
- src={msg.image_url}
157
- alt="Chat image"
158
- style={{ width: 200 }}
159
- className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
234
+ <div key={i}> {/* WRAP in div to add suggestions after */}
235
+ <div
236
+ className={`chat-msg assistant ${isFirstMessage ? 'first-message' : ''}`}
237
+ >
238
+ {msg.image_url && (
239
+ <img
240
+ src={msg.image_url}
241
+ alt="Chat image"
242
+ style={{ width: 200 }}
243
+ className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
244
+ />
245
+ )}
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
+ )}
270
+ </div>
271
+
272
+ {/* ADD suggestions here - MOVED OUTSIDE the chat-msg div */}
273
+ {isLastAssistantMessage && messagesSuggestions.length > 0 && (
274
+ <InlineSuggestions
275
+ suggestions={getRandomSuggestions(2)}
276
+ colorCode={colorCode}
277
+ onSuggestionClick={sendMessageSuggestion}
160
278
  />
161
279
  )}
162
- <ChatImage message={msg.content} />
163
- <div className="flex justify-start">
164
- <ChatImageTextAssist message={msg.content} />
165
- </div>
166
280
  </div>
167
281
  );
168
282
  }
169
283
 
170
-
171
-
284
+ if (msg?.audio_url) {
285
+ return (
286
+ <div
287
+ key={i}
288
+ className="chat-msg user"
289
+ style={{
290
+ backgroundColor: 'transparent',
291
+ padding: 0
292
+ }}
293
+ >
294
+ <div style={{ borderRadius: 12 }}>
295
+ <AudioVisualizer link={msg.audio_url} transcription={msg.content} colorCode={colorCode} />
296
+ </div>
297
+ <div className="mt-1 mr-2 flex items-end justify-end">
298
+ {msg.status === 'uploading' ? (
299
+ <Spin indicator={<LoadingOutlined spin />} size="small" />
300
+ ) : msg.status === 'delivered' ? (
301
+ <CheckCircleOutlined style={{ color: 'green' }} />
302
+ ) : msg.status === 'failed' ? (
303
+ <CloseCircleOutlined style={{ color: 'red' }} />
304
+ ) : null}
305
+ </div>
306
+ </div>
307
+ );
308
+ }
172
309
 
173
-
174
- return (
175
- <div key={i} className="chat-msg assistant">
176
- {msg.image_url && (
310
+ if (msg.image_url) {
311
+ return (
312
+ <div key={i} className="chat-msg user" style={{ backgroundColor: 'transparent' }}>
177
313
  <img
178
314
  src={msg.image_url}
179
315
  alt="Chat image"
180
316
  style={{ width: 200 }}
181
- className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
182
- />
183
- )}
184
-
185
- {userType === "shopify" ? (
186
- <AssistantMessage
187
- message={msg.content || msg.text || ""}
188
- colorCode={colorCode}
189
- disableCheckout={disableCheckout}
190
- handleAddToCart={handleAddToCart}
191
- handleRemoveFromCart={handleRemoveFromCart}
192
- />
193
- ) : userType === "salla" ? (
194
- <SallaAsssiatnt
195
- message={msg.content || msg.text || ""}
196
- colorCode={colorCode}
197
- handleAddToCart={handleAddToCart}
198
- handleRemoveFromCart={handleRemoveFromCart}
199
- />
200
- ) : (
201
- <ZidAssistantChatMessage
202
- message={msg.content || msg.text || ""}
203
- colorCode={colorCode}
204
- handleAddToCart={handleAddToCart}
205
- handleRemoveFromCart={handleRemoveFromCart}
317
+ className={`rounded-md mt-2 max-w-xs ${msg.content === 'user'
318
+ ? 'border border-gray-300 shadow-sm'
319
+ : 'border border-green-400 shadow-sm'
320
+ }`}
206
321
  />
207
- )}
208
-
209
-
210
-
211
- </div>
212
- );
213
- }
214
-
215
-
216
-
217
- if(msg?.audio_url){
218
-
219
- return(
220
- <div
221
- className={`chat-msg user`}
222
- style={{
223
- backgroundColor:'transparent',
224
- padding:0
225
- }}
226
- >
227
-
228
- <div style={{
229
- borderRadius:12
230
- }}>
231
-
232
- <AudioVisualizer link={msg.audio_url} transcription={msg.content} colorCode={colorCode} />
233
-
234
- {/* Upload/Delivery Status */}
235
-
236
- </div>
237
-
238
- <div className="mt-1 mr-2 flex items-end justify-end">
239
- {msg.status === 'uploading' ? (
240
- <Spin indicator={<LoadingOutlined spin />} size="small" />
241
- ) : msg.status === 'delivered' ? (
242
- <CheckCircleOutlined style={{ color: 'green' }} />
243
- ) : msg.status === 'failed' ? (
244
- <CloseCircleOutlined style={{ color: 'red' }} />
245
- ) : null}
246
- </div>
247
- </div>
248
- )
322
+ <div className="mt-1 mr-2 flex items-end justify-end">
323
+ {msg.status === 'uploading' ? (
324
+ <Spin indicator={<LoadingOutlined spin />} size="small" />
325
+ ) : msg.status === 'delivered' ? (
326
+ <CheckCircleOutlined style={{ color: 'green' }} />
327
+ ) : msg.status === 'failed' ? (
328
+ <CloseCircleOutlined style={{ color: 'red' }} />
329
+ ) : null}
330
+ </div>
331
+ {msg.content && (
332
+ <div className="flex justify-end mt-2">
333
+ <div
334
+ style={{
335
+ color: 'white',
336
+ border: 1,
337
+ backgroundColor: colorCode,
338
+ paddingTop: 10,
339
+ paddingBottom: 10,
340
+ paddingInline: 14,
341
+ maxWidth: '85%',
342
+ lineHeight: 1.4,
343
+ borderRadius: '16px',
344
+ wordBreak: 'break-word',
345
+ }}
346
+ >
347
+ <span>{msg.content}</span>
348
+ </div>
349
+ </div>
350
+ )}
351
+ </div>
352
+ );
249
353
  }
250
-
251
354
 
252
- // ✅ IMAGE MESSAGE (non-assistant)
253
- if (msg.image_url) {
254
- return (
255
- <div key={i} className="chat-msg user" style={{ backgroundColor: 'transparent' }}>
256
- <img
257
- src={msg.image_url}
258
- alt="Chat image"
259
- style={{ width: 200 }}
260
- className={`rounded-md mt-2 max-w-xs ${
261
- msg.content === 'user'
262
- ? 'border border-gray-300 shadow-sm'
263
- : 'border border-green-400 shadow-sm'
264
- }`}
265
- />
266
-
267
- <div className="mt-1 mr-2 flex items-end justify-end">
268
- {msg.status === 'uploading' ? (
269
- <Spin indicator={<LoadingOutlined spin />} size="small" />
270
- ) : msg.status === 'delivered' ? (
271
- <CheckCircleOutlined style={{ color: 'green' }} />
272
- ) : msg.status === 'failed' ? (
273
- <CloseCircleOutlined style={{ color: 'red' }} />
274
- ) : null}
275
- </div>
276
-
277
- {msg.content && (
278
- <div className="flex justify-end mt-2">
279
- <div
280
- style={{
281
- color: 'white',
282
- border: 1,
283
- backgroundColor: colorCode,
284
- paddingTop: 10,
285
- paddingBottom: 10,
286
- paddingInline: 14,
287
- maxWidth: '85%',
288
- lineHeight: 1.4,
289
- borderRadius: '16px',
290
- wordBreak: 'break-word',
291
- }}
292
- >
293
- <span>{msg.content}</span>
355
+ if (Array.isArray(msg.content)) {
356
+ return (
357
+ <div key={i} className='chat-msg user' style={{ backgroundColor: 'transparent', padding: 0 }}>
358
+ <div style={{ backgroundColor: 'transparent', padding: 0 }}>
359
+ <ChatImage message={msg.content} />
360
+ </div>
361
+ <div className='flex justify-end'>
362
+ <ChatImageText message={msg.content} colorCode={colorCode} />
363
+ </div>
294
364
  </div>
295
- </div>
296
- )}
297
- </div>
298
- );
299
- }
300
-
365
+ );
366
+ }
301
367
 
302
- // ✅ MULTIPLE IMAGES (array)
303
- if (Array.isArray(msg.content)) {
304
- return (
305
- <div key={i} className='chat-msg user' style={{backgroundColor:'transparent',padding:0}}>
306
- <div style={{backgroundColor:'transparent',padding:0}}>
307
- <ChatImage message={msg.content} />
308
- </div>
309
- <div key={i} className='flex justify-end'>
310
- <ChatImageText message={msg.content} colorCode={colorCode} />
368
+ return (
369
+ <div
370
+ key={i}
371
+ className={`chat-msg ${msg.role}`}
372
+ style={
373
+ msg.role === 'user'
374
+ ? { backgroundColor: colorCode, color: '#fff' }
375
+ : {}
376
+ }
377
+ >
378
+ <AddToCartConfirmation message={msg.content} colorCode={colorCode} />
311
379
  </div>
380
+ );
381
+ })}
312
382
 
313
- </div>
314
- );
315
- }
316
-
317
-
318
- return (
319
- <div
320
- key={i}
321
- className={`chat-msg ${msg.role}`}
322
- style={
323
- msg.role === 'user'
324
- ? { backgroundColor: colorCode, color: '#fff' }
325
- : {}
326
- }
327
- >
328
-
329
- <AddToCartConfirmation message={msg.content} colorCode={colorCode}/>
330
-
331
- </div>
332
- );
333
- })}
334
- <AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
335
- <div ref={bottomRef} />
336
-
383
+ <AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
384
+ <div ref={bottomRef} />
337
385
  </div>
338
386
  );
339
387
  };
340
388
 
341
- export default ChatMessages;
389
+ export default ChatMessages;