shopify-chatbot-widget 0.7.8 → 0.8.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/dist/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +117 -117
- package/package.json +3 -1
- package/src/App.css +42 -0
- package/src/App.tsx +13 -0
- package/src/Chatbot/Chatbot.css +270 -0
- package/src/Chatbot/Chatbot.tsx +214 -0
- package/src/Chatbot/ChatbotTransition.css +19 -0
- package/src/Chatbot/OLDCHAT.tsx +259 -0
- package/src/Chatbot/components/ChatInput.css +127 -0
- package/src/Chatbot/components/ChatInput.tsx +354 -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 +206 -0
- package/src/Chatbot/components/Messages/AudioMessage.tsx +123 -0
- package/src/Chatbot/components/Messages/CalendlyEmbeds.tsx +66 -0
- package/src/Chatbot/components/Messages/CardSwiper.tsx +194 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +264 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +208 -0
- package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +208 -0
- package/src/Chatbot/components/Messages/Carousel/SallaSwiper.css +245 -0
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +108 -0
- package/src/Chatbot/components/Messages/CartWidget.tsx +151 -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 +31 -0
- package/src/Chatbot/components/Messages/ChatMessages.tsx +334 -0
- package/src/Chatbot/components/Messages/ProductCard.css +149 -0
- package/src/Chatbot/components/Messages/SallaAssistantMessage.tsx +196 -0
- package/src/Chatbot/components/Messages/Support.tsx +177 -0
- package/src/Chatbot/components/Messages/Typing.css +31 -0
- package/src/Chatbot/components/Messages/Typing.tsx +19 -0
- package/src/Chatbot/components/Messages/ZidAssistantMesage.tsx +196 -0
- package/src/Chatbot/components/Messages/cartwidget.css +123 -0
- package/src/Chatbot/components/Messenger/Messenger.tsx +531 -0
- package/src/Chatbot/components/Notification.tsx +28 -0
- package/src/Chatbot/components/Powered.css +29 -0
- package/src/Chatbot/components/Powered.tsx +12 -0
- package/src/Chatbot/components/Sessions/CreateSession.tsx +201 -0
- package/src/Chatbot/components/Sessions/RenderList.tsx +212 -0
- package/src/Chatbot/components/Suggestions.css +35 -0
- package/src/Chatbot/components/Suggestions.tsx +57 -0
- package/src/Chatbot/notification.css +17 -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 +107 -0
- package/src/hooks/useChatbotAPI.tsx +167 -0
- package/src/hooks/useChatbotData.tsx +132 -0
- package/src/hooks/useSessionMessages.tsx +50 -0
- package/src/hooks/useWebsocke.tsx +45 -0
- package/src/i18n.tsx +30 -0
- package/src/index.css +2 -0
- package/src/main.tsx +10 -0
- package/src/types/chatbot.ts +40 -0
- package/src/utils/cookies.tsx +14 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import React,{useEffect, useRef} from 'react';
|
|
2
|
+
import { Spin } from 'antd';
|
|
3
|
+
import { LoadingOutlined ,CheckCircleOutlined,CloseCircleOutlined} from '@ant-design/icons';
|
|
4
|
+
import Typing from './Typing';
|
|
5
|
+
import AssistantMessage from './AssistantMesage';
|
|
6
|
+
import ChatImage from './ChatImage';
|
|
7
|
+
import './ChatMessages.css'
|
|
8
|
+
import ChatImageText from './ChatImageText';
|
|
9
|
+
import AudioVisualizer from './AudioMessage';
|
|
10
|
+
import AgentStatusBanner from './AgentStatus';
|
|
11
|
+
import AddToCartConfirmation from './Addcart';
|
|
12
|
+
import SupportConnect from './Support';
|
|
13
|
+
import ChatImageTextAssist from './ChatImageTextAssis';
|
|
14
|
+
import ZidAssistantChatMessage from './ZidAssistantMesage';
|
|
15
|
+
import SallaAsssiatnt from './SallaAssistantMessage';
|
|
16
|
+
import CalendlyEmbeds from './CalendlyEmbeds';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
interface Message {
|
|
20
|
+
role?: string;
|
|
21
|
+
content?: any;
|
|
22
|
+
text?: string;
|
|
23
|
+
typing?: boolean;
|
|
24
|
+
image_url?:string;
|
|
25
|
+
status?:string;
|
|
26
|
+
audio_url?:string;
|
|
27
|
+
isConnect?:boolean;
|
|
28
|
+
calendly?:boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ChatMessagesProps {
|
|
32
|
+
messages: Message[];
|
|
33
|
+
colorCode: string;
|
|
34
|
+
isLoading: boolean;
|
|
35
|
+
handleAddToCart: (
|
|
36
|
+
variantId: string,
|
|
37
|
+
name: string,
|
|
38
|
+
qty?: number
|
|
39
|
+
) => Promise<void>;
|
|
40
|
+
handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
|
|
41
|
+
sessionId:string;
|
|
42
|
+
isBusiness?: boolean;
|
|
43
|
+
agentStatus?: {
|
|
44
|
+
status?: string;
|
|
45
|
+
user?: string;
|
|
46
|
+
};
|
|
47
|
+
userType:string;
|
|
48
|
+
calendly:string;
|
|
49
|
+
disableCheckout: boolean;
|
|
50
|
+
isfetchingMessage:boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
const ChatMessages: React.FC<ChatMessagesProps> = ({
|
|
58
|
+
messages,
|
|
59
|
+
colorCode,
|
|
60
|
+
handleAddToCart,
|
|
61
|
+
handleRemoveFromCart,
|
|
62
|
+
isLoading,
|
|
63
|
+
agentStatus,
|
|
64
|
+
sessionId,
|
|
65
|
+
userType,
|
|
66
|
+
calendly,
|
|
67
|
+
disableCheckout,
|
|
68
|
+
isfetchingMessage,
|
|
69
|
+
|
|
70
|
+
}) => {
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const bottomRef = useRef<HTMLDivElement>(null);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!bottomRef.current) return;
|
|
79
|
+
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
82
|
+
}, 150); // Slight delay ensures DOM settles in Shopify
|
|
83
|
+
|
|
84
|
+
return () => clearTimeout(timeout);
|
|
85
|
+
}, [messages]);
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if (isfetchingMessage || isLoading || !messages) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
className="loader-wrapper"
|
|
93
|
+
style={{
|
|
94
|
+
display: "flex",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
justifyContent: "center",
|
|
97
|
+
height: "100%",
|
|
98
|
+
gap: "8px",
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<div style={{backgroundColor:colorCode}} className="box-loader"></div>
|
|
102
|
+
<div style={{backgroundColor:colorCode}} className="box-loader"></div>
|
|
103
|
+
<div style={{backgroundColor:colorCode}} className="box-loader"></div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className="chatbot-messages space-y-4">
|
|
111
|
+
{messages.map((msg, i) => {
|
|
112
|
+
if (msg.typing) {
|
|
113
|
+
return (
|
|
114
|
+
<div key={i} className="chat-msg typing">
|
|
115
|
+
<Typing color={colorCode} />
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (msg.isConnect){
|
|
121
|
+
return(
|
|
122
|
+
<SupportConnect sessionId={sessionId}/>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if (msg.role === 'assistant') {
|
|
130
|
+
// Handle array content separately
|
|
131
|
+
if (Array.isArray(msg.content)) {
|
|
132
|
+
return (
|
|
133
|
+
<div key={i} className="chat-msg assistant" style={{ backgroundColor: 'transparent', padding: 0 }}>
|
|
134
|
+
{msg.image_url && (
|
|
135
|
+
<img
|
|
136
|
+
src={msg.image_url}
|
|
137
|
+
alt="Chat image"
|
|
138
|
+
style={{ width: 200 }}
|
|
139
|
+
className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
<ChatImage message={msg.content} />
|
|
143
|
+
<div className="flex justify-start">
|
|
144
|
+
<ChatImageTextAssist message={msg.content} />
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (msg.calendly) {
|
|
151
|
+
return (
|
|
152
|
+
<div
|
|
153
|
+
key={i}
|
|
154
|
+
style={{
|
|
155
|
+
borderRadius: 5,
|
|
156
|
+
color: "white",
|
|
157
|
+
width: "100%",
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
<CalendlyEmbeds calendlyLink={calendly} />
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<div key={i} className="chat-msg assistant">
|
|
169
|
+
{msg.image_url && (
|
|
170
|
+
<img
|
|
171
|
+
src={msg.image_url}
|
|
172
|
+
alt="Chat image"
|
|
173
|
+
style={{ width: 200 }}
|
|
174
|
+
className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
{userType === "shopify" ? (
|
|
179
|
+
<AssistantMessage
|
|
180
|
+
message={msg.content || msg.text || ""}
|
|
181
|
+
colorCode={colorCode}
|
|
182
|
+
disableCheckout={disableCheckout}
|
|
183
|
+
handleAddToCart={handleAddToCart}
|
|
184
|
+
handleRemoveFromCart={handleRemoveFromCart}
|
|
185
|
+
/>
|
|
186
|
+
) : userType === "salla" ? (
|
|
187
|
+
<SallaAsssiatnt
|
|
188
|
+
message={msg.content || msg.text || ""}
|
|
189
|
+
colorCode={colorCode}
|
|
190
|
+
handleAddToCart={handleAddToCart}
|
|
191
|
+
handleRemoveFromCart={handleRemoveFromCart}
|
|
192
|
+
/>
|
|
193
|
+
) : (
|
|
194
|
+
<ZidAssistantChatMessage
|
|
195
|
+
message={msg.content || msg.text || ""}
|
|
196
|
+
colorCode={colorCode}
|
|
197
|
+
handleAddToCart={handleAddToCart}
|
|
198
|
+
handleRemoveFromCart={handleRemoveFromCart}
|
|
199
|
+
/>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if(msg?.audio_url){
|
|
211
|
+
|
|
212
|
+
return(
|
|
213
|
+
<div
|
|
214
|
+
className={`chat-msg user`}
|
|
215
|
+
style={{
|
|
216
|
+
backgroundColor:'transparent',
|
|
217
|
+
padding:0
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
|
|
221
|
+
<div style={{
|
|
222
|
+
borderRadius:12
|
|
223
|
+
}}>
|
|
224
|
+
|
|
225
|
+
<AudioVisualizer link={msg.audio_url} transcription={msg.content} colorCode={colorCode} />
|
|
226
|
+
|
|
227
|
+
{/* Upload/Delivery Status */}
|
|
228
|
+
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div className="mt-1 mr-2 flex items-end justify-end">
|
|
232
|
+
{msg.status === 'uploading' ? (
|
|
233
|
+
<Spin indicator={<LoadingOutlined spin />} size="small" />
|
|
234
|
+
) : msg.status === 'delivered' ? (
|
|
235
|
+
<CheckCircleOutlined style={{ color: 'green' }} />
|
|
236
|
+
) : msg.status === 'failed' ? (
|
|
237
|
+
<CloseCircleOutlined style={{ color: 'red' }} />
|
|
238
|
+
) : null}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
// ✅ IMAGE MESSAGE (non-assistant)
|
|
246
|
+
if (msg.image_url) {
|
|
247
|
+
return (
|
|
248
|
+
<div key={i} className="chat-msg user" style={{ backgroundColor: 'transparent' }}>
|
|
249
|
+
<img
|
|
250
|
+
src={msg.image_url}
|
|
251
|
+
alt="Chat image"
|
|
252
|
+
style={{ width: 200 }}
|
|
253
|
+
className={`rounded-md mt-2 max-w-xs ${
|
|
254
|
+
msg.content === 'user'
|
|
255
|
+
? 'border border-gray-300 shadow-sm'
|
|
256
|
+
: 'border border-green-400 shadow-sm'
|
|
257
|
+
}`}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
<div className="mt-1 mr-2 flex items-end justify-end">
|
|
261
|
+
{msg.status === 'uploading' ? (
|
|
262
|
+
<Spin indicator={<LoadingOutlined spin />} size="small" />
|
|
263
|
+
) : msg.status === 'delivered' ? (
|
|
264
|
+
<CheckCircleOutlined style={{ color: 'green' }} />
|
|
265
|
+
) : msg.status === 'failed' ? (
|
|
266
|
+
<CloseCircleOutlined style={{ color: 'red' }} />
|
|
267
|
+
) : null}
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{msg.content && (
|
|
271
|
+
<div className="flex justify-end mt-2">
|
|
272
|
+
<div
|
|
273
|
+
style={{
|
|
274
|
+
color: 'white',
|
|
275
|
+
border: 1,
|
|
276
|
+
backgroundColor: colorCode,
|
|
277
|
+
paddingTop: 10,
|
|
278
|
+
paddingBottom: 10,
|
|
279
|
+
paddingInline: 14,
|
|
280
|
+
maxWidth: '85%',
|
|
281
|
+
lineHeight: 1.4,
|
|
282
|
+
borderRadius: '16px',
|
|
283
|
+
wordBreak: 'break-word',
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
<span>{msg.content}</span>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
// ✅ MULTIPLE IMAGES (array)
|
|
296
|
+
if (Array.isArray(msg.content)) {
|
|
297
|
+
return (
|
|
298
|
+
<div key={i} className='chat-msg user' style={{backgroundColor:'transparent',padding:0}}>
|
|
299
|
+
<div style={{backgroundColor:'transparent',padding:0}}>
|
|
300
|
+
<ChatImage message={msg.content} />
|
|
301
|
+
</div>
|
|
302
|
+
<div key={i} className='flex justify-end'>
|
|
303
|
+
<ChatImageText message={msg.content} colorCode={colorCode} />
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
key={i}
|
|
314
|
+
className={`chat-msg ${msg.role}`}
|
|
315
|
+
style={
|
|
316
|
+
msg.role === 'user'
|
|
317
|
+
? { backgroundColor: colorCode, color: '#fff' }
|
|
318
|
+
: {}
|
|
319
|
+
}
|
|
320
|
+
>
|
|
321
|
+
|
|
322
|
+
<AddToCartConfirmation message={msg.content} colorCode={colorCode}/>
|
|
323
|
+
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
})}
|
|
327
|
+
<AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
|
|
328
|
+
<div ref={bottomRef} />
|
|
329
|
+
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
export default ChatMessages;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
.product-card {
|
|
2
|
+
background-color: white;
|
|
3
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
|
4
|
+
border-radius: 16px;
|
|
5
|
+
padding: 16px;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
gap: 16px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (min-width: 640px) {
|
|
12
|
+
.product-card {
|
|
13
|
+
flex-direction: row;
|
|
14
|
+
align-items: flex-start;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.product-image-container {
|
|
19
|
+
width: 128px;
|
|
20
|
+
height: 128px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (min-width: 640px) {
|
|
24
|
+
.product-image-container {
|
|
25
|
+
width: 160px;
|
|
26
|
+
height: 160px;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.product-image {
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
object-fit: cover;
|
|
34
|
+
border-radius: 12px;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.product-details {
|
|
39
|
+
flex: 1;
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 12px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.product-name {
|
|
46
|
+
font-size: 1.125rem;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
color: #111827;
|
|
49
|
+
text-decoration: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.product-price {
|
|
53
|
+
font-size: 1rem;
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
color: #374151;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.label {
|
|
59
|
+
font-size: 0.875rem;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
color: #6b7280;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.color-options,
|
|
65
|
+
.size-options {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-wrap: wrap;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
margin-top: 4px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.color-button,
|
|
73
|
+
.size-button {
|
|
74
|
+
padding: 4px 12px;
|
|
75
|
+
font-size: 0.875rem;
|
|
76
|
+
border-radius: 9999px;
|
|
77
|
+
border: 1px solid #d1d5db;
|
|
78
|
+
background-color: #f3f4f6;
|
|
79
|
+
color: #1f2937;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.color-button.disabled,
|
|
84
|
+
.size-button.disabled {
|
|
85
|
+
opacity: 0.4;
|
|
86
|
+
cursor: not-allowed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.color-button.active,
|
|
90
|
+
.size-button.active {
|
|
91
|
+
color: white;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.cart-controls {
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 12px;
|
|
98
|
+
margin-top: 12px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.cart-button {
|
|
102
|
+
padding: 4px 12px;
|
|
103
|
+
border: 1px solid #d1d5db;
|
|
104
|
+
border-radius: 8px;
|
|
105
|
+
background: none;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.cart-count {
|
|
110
|
+
min-width: 20px;
|
|
111
|
+
text-align: center;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.increment {
|
|
116
|
+
background-color: #7f28f8;
|
|
117
|
+
color: white;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
/* ProductCard.css */
|
|
122
|
+
.carousel-container {
|
|
123
|
+
position: relative;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.carousel-track {
|
|
128
|
+
display: flex;
|
|
129
|
+
transition: transform 0.3s ease-in-out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.carousel-slide {
|
|
133
|
+
flex: 0 0 100%;
|
|
134
|
+
padding: 0 1rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.carousel-nav {
|
|
138
|
+
position: absolute;
|
|
139
|
+
top: 50%;
|
|
140
|
+
transform: translateY(-50%);
|
|
141
|
+
background: rgba(255,255,255,0.8);
|
|
142
|
+
border: none;
|
|
143
|
+
font-size: 2rem;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
z-index: 10;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.carousel-nav.prev { left: 0.5rem; }
|
|
149
|
+
.carousel-nav.next { right: 0.5rem; }
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import React, { useState, useMemo, type FC } from 'react';
|
|
2
|
+
import './ProductCard.css';
|
|
3
|
+
import SallaProductCarousel from './Carousel/CardSwiperSalla';
|
|
4
|
+
import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons';
|
|
5
|
+
import { Button } from 'antd';
|
|
6
|
+
import { useKeenSlider } from 'keen-slider/react';
|
|
7
|
+
import 'keen-slider/keen-slider.min.css';
|
|
8
|
+
|
|
9
|
+
interface ProductVariant {
|
|
10
|
+
name: string;
|
|
11
|
+
price: string;
|
|
12
|
+
link: string;
|
|
13
|
+
image: string;
|
|
14
|
+
variant_id: string;
|
|
15
|
+
variant_tag_name: string;
|
|
16
|
+
option?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface AssistantMessageProps {
|
|
20
|
+
message: string;
|
|
21
|
+
colorCode: string;
|
|
22
|
+
isBusiness?: boolean;
|
|
23
|
+
handleAddToCart: (variantId: string, name: string, qty?: number) => Promise<void>;
|
|
24
|
+
handleRemoveFromCart: (variantId: string, qty?: number) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SallaAsssiatnt: FC<AssistantMessageProps> = ({
|
|
28
|
+
message,
|
|
29
|
+
colorCode,
|
|
30
|
+
handleAddToCart,
|
|
31
|
+
handleRemoveFromCart,
|
|
32
|
+
}) => {
|
|
33
|
+
const [counts, setCounts] = useState<Record<string, number>>({});
|
|
34
|
+
const [selectedVariants, setSelectedVariants] = useState<Record<string, string>>({});
|
|
35
|
+
|
|
36
|
+
const formatTextWithLinks = (text: string): React.ReactNode[] => {
|
|
37
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
38
|
+
return text.split(urlRegex).map((part, i) =>
|
|
39
|
+
urlRegex.test(part) ? (
|
|
40
|
+
<a
|
|
41
|
+
key={i}
|
|
42
|
+
href={part.trim()}
|
|
43
|
+
style={{ color: '#00b3bc' }}
|
|
44
|
+
target="_blank"
|
|
45
|
+
rel="noopener noreferrer"
|
|
46
|
+
className="underline break-words"
|
|
47
|
+
>
|
|
48
|
+
{part}
|
|
49
|
+
</a>
|
|
50
|
+
) : (
|
|
51
|
+
<span key={i}>{part}</span>
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const extractProductDetails = (text: string): ProductVariant | null => {
|
|
57
|
+
const normalized = text.trim();
|
|
58
|
+
if (!normalized.includes('Name:') || !normalized.includes('VariantId')) return null;
|
|
59
|
+
const nameMatch = normalized.match(/Name\s*[:\-]\s*(.+)/i);
|
|
60
|
+
const priceMatch = normalized.match(/Price\s*[:\-]\s*(.+)/i);
|
|
61
|
+
const linkMatch = normalized.match(/Link\s*[:\-]\s*(https?:\/\/[^\s]+)/i);
|
|
62
|
+
const variantMatch = normalized.match(/VariantId\s*[:\-]\s*(.+)/i);
|
|
63
|
+
const tagNameMatch = normalized.match(/variant_tag_name\s*[:\-]\s*(.+)/i);
|
|
64
|
+
const OptionMatch = normalized.match(/options\s*[:\-]\s*([^|\r\n]+)/i);
|
|
65
|
+
if (!nameMatch || !priceMatch || !linkMatch || !variantMatch || !tagNameMatch) return null;
|
|
66
|
+
return {
|
|
67
|
+
name: nameMatch[1].trim(),
|
|
68
|
+
price: priceMatch[1].trim(),
|
|
69
|
+
link: linkMatch[1].trim(),
|
|
70
|
+
image: '',
|
|
71
|
+
variant_id: variantMatch[1].trim(),
|
|
72
|
+
variant_tag_name: tagNameMatch[1].trim(),
|
|
73
|
+
option: OptionMatch?.[1]?.trim(),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const parsedSegments = useMemo(() => {
|
|
78
|
+
const parts = message.split('|||').map(p => p.trim()).filter(Boolean);
|
|
79
|
+
const segments: { type: 'text' | 'product'; data: any }[] = [];
|
|
80
|
+
let lastProduct: ProductVariant | null = null;
|
|
81
|
+
|
|
82
|
+
parts.forEach(part => {
|
|
83
|
+
if (part.startsWith('img - ')) {
|
|
84
|
+
const url = part.replace(/^img\s*[-:]\s*/, '').trim();
|
|
85
|
+
if (lastProduct) lastProduct.image = url;
|
|
86
|
+
else {
|
|
87
|
+
const lastSeg = [...segments].reverse().find(s => s.type === 'product');
|
|
88
|
+
if (lastSeg) (lastSeg.data as ProductVariant).image = url;
|
|
89
|
+
}
|
|
90
|
+
} else if (part.includes('Name:') && part.includes('VariantId')) {
|
|
91
|
+
const product = extractProductDetails(part);
|
|
92
|
+
if (product) {
|
|
93
|
+
segments.push({ type: 'product', data: product });
|
|
94
|
+
lastProduct = product;
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
segments.push({ type: 'text', data: part });
|
|
98
|
+
lastProduct = null;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return segments;
|
|
103
|
+
}, [message]);
|
|
104
|
+
|
|
105
|
+
const groupedProducts = useMemo(() => {
|
|
106
|
+
const groups: Record<string, ProductVariant[]> = {};
|
|
107
|
+
parsedSegments.forEach(seg => {
|
|
108
|
+
if (seg.type === 'product') {
|
|
109
|
+
const p = seg.data as ProductVariant;
|
|
110
|
+
groups[p.variant_tag_name] = groups[p.variant_tag_name] || [];
|
|
111
|
+
groups[p.variant_tag_name].push(p);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return groups;
|
|
115
|
+
}, [parsedSegments]);
|
|
116
|
+
|
|
117
|
+
const shortenName = (name: string, maxLength = 22) =>
|
|
118
|
+
name.length > maxLength ? name.slice(0, maxLength) + '...' : name;
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
const [currentSlide, setCurrentSlide] = useState(0);
|
|
123
|
+
const totalSlides =Object.keys(groupedProducts).length ;
|
|
124
|
+
const [sliderRef, slider] = useKeenSlider<HTMLDivElement>({
|
|
125
|
+
loop: false,
|
|
126
|
+
mode: 'free',
|
|
127
|
+
slides: { perView: 1, spacing: 16 },
|
|
128
|
+
slideChanged(s) {
|
|
129
|
+
setCurrentSlide(s.track.details.rel);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="space-y-6 mt-4 h-fit">
|
|
136
|
+
{parsedSegments
|
|
137
|
+
.filter(seg => seg.type === 'text')
|
|
138
|
+
.map((seg, i) => (
|
|
139
|
+
<p key={i} className="whitespace-pre-wrap break-words">
|
|
140
|
+
{formatTextWithLinks(seg.data)}
|
|
141
|
+
</p>
|
|
142
|
+
))}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
{Object.keys(groupedProducts).length > 0 && (
|
|
147
|
+
<>
|
|
148
|
+
{Object.keys(groupedProducts).length > 1 && (
|
|
149
|
+
<div
|
|
150
|
+
style={{
|
|
151
|
+
display: 'flex',
|
|
152
|
+
margin: 0,
|
|
153
|
+
marginBottom: '6px',
|
|
154
|
+
justifyContent: 'space-between',
|
|
155
|
+
paddingInline: '4px',
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
<Button
|
|
159
|
+
shape="circle"
|
|
160
|
+
icon={<ArrowLeftOutlined />}
|
|
161
|
+
onClick={() => slider.current?.prev()}
|
|
162
|
+
disabled={currentSlide === 0}
|
|
163
|
+
/>
|
|
164
|
+
<Button
|
|
165
|
+
shape="circle"
|
|
166
|
+
icon={<ArrowRightOutlined />}
|
|
167
|
+
onClick={() => slider.current?.next()}
|
|
168
|
+
disabled={currentSlide === totalSlides - 1}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
<div ref={sliderRef} className="keen-slider">
|
|
173
|
+
<SallaProductCarousel
|
|
174
|
+
groupedProducts={groupedProducts}
|
|
175
|
+
selectedVariants={selectedVariants}
|
|
176
|
+
setSelectedVariants={setSelectedVariants}
|
|
177
|
+
counts={counts}
|
|
178
|
+
setCounts={setCounts}
|
|
179
|
+
handleAddToCart={handleAddToCart}
|
|
180
|
+
handleRemoveFromCart={handleRemoveFromCart}
|
|
181
|
+
colorCode={colorCode}
|
|
182
|
+
shortenName={shortenName}
|
|
183
|
+
sliderInstance={slider} // pass it down if needed
|
|
184
|
+
currentSlide={currentSlide}
|
|
185
|
+
totalSlides={totalSlides}
|
|
186
|
+
sliderRef={sliderRef}
|
|
187
|
+
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export default SallaAsssiatnt;
|