shopify-chatbot-widget 0.8.3 → 0.8.5
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 +168 -166
- package/package.json +1 -1
- package/src/Chatbot/Chatbot.tsx +1 -1
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +4 -8
- package/src/Chatbot/components/Messages/Carousel/CardSwiperSalla.tsx +50 -29
- package/src/Chatbot/components/Messages/Carousel/CardSwiperZid.tsx +55 -27
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +97 -27
- package/src/Chatbot/components/Messages/ChatMessages.tsx +16 -14
- package/src/hooks/useChatbotAPI.tsx +26 -17
- package/src/types/chatbot.ts +1 -0
package/package.json
CHANGED
package/src/Chatbot/Chatbot.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { Spin } from 'antd';
|
|
|
11
11
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
12
12
|
|
|
13
13
|
const Chatbot: React.FC = () => {
|
|
14
|
-
const [open, setOpen] = useState(
|
|
14
|
+
const [open, setOpen] = useState(true);
|
|
15
15
|
const [viewMode, setViewMode] = useState<'sessions' | 'create' | 'messenger'>('sessions');
|
|
16
16
|
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
|
17
17
|
const [HasInfo, setHasInfo] = useState(false);
|
|
@@ -129,11 +129,7 @@ const ProductCarousel: FC<Props> = ({
|
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
const isTryOnOpen = openTryOn === currentVariant.variant_id;
|
|
132
|
-
|
|
133
|
-
currentVariant.tag
|
|
134
|
-
? currentVariant.tag.toLowerCase() === "clothing"
|
|
135
|
-
: true; // show by default unless you want to gate strictly by tag
|
|
136
|
-
|
|
132
|
+
|
|
137
133
|
return (
|
|
138
134
|
<article
|
|
139
135
|
key={tag}
|
|
@@ -233,7 +229,7 @@ const ProductCarousel: FC<Props> = ({
|
|
|
233
229
|
)}
|
|
234
230
|
|
|
235
231
|
{/* Try On toggle button (kept) */}
|
|
236
|
-
|
|
232
|
+
|
|
237
233
|
<Button
|
|
238
234
|
style={{ backgroundColor: colorCode, color: "white" }}
|
|
239
235
|
className="px-6 py-2 rounded-lg shadow-md transition"
|
|
@@ -243,11 +239,11 @@ const ProductCarousel: FC<Props> = ({
|
|
|
243
239
|
>
|
|
244
240
|
{isTryOnOpen ? "Close Try On" : "Try On"}
|
|
245
241
|
</Button>
|
|
246
|
-
|
|
242
|
+
|
|
247
243
|
</div>
|
|
248
244
|
|
|
249
245
|
{/* Try On panel */}
|
|
250
|
-
{
|
|
246
|
+
{ isTryOnOpen && (
|
|
251
247
|
<div className="mt-4">
|
|
252
248
|
<TryOnBox productID={currentVariant.variant_id} productImage={currentVariant.image} />
|
|
253
249
|
</div>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React,{useState} from 'react';
|
|
2
2
|
import type { FC } from 'react';
|
|
3
3
|
import 'keen-slider/keen-slider.min.css';
|
|
4
4
|
import type { KeenSliderInstance } from 'keen-slider/react';
|
|
5
5
|
import './SallaSwiper.css';
|
|
6
|
+
import { Button } from 'antd';
|
|
7
|
+
import TryOnBox from "./TryOn";
|
|
6
8
|
|
|
7
9
|
interface ProductVariant {
|
|
8
10
|
variant_id: string;
|
|
@@ -35,10 +37,10 @@ const SallaProductCarousel: FC<Props> = ({
|
|
|
35
37
|
groupedProducts,
|
|
36
38
|
selectedVariants,
|
|
37
39
|
setSelectedVariants,
|
|
38
|
-
counts,
|
|
39
|
-
setCounts,
|
|
40
|
-
handleAddToCart,
|
|
41
|
-
handleRemoveFromCart,
|
|
40
|
+
// counts,
|
|
41
|
+
// setCounts,
|
|
42
|
+
// handleAddToCart,
|
|
43
|
+
// handleRemoveFromCart,
|
|
42
44
|
colorCode = '#7F28F8',
|
|
43
45
|
shortenName,
|
|
44
46
|
sliderRef,
|
|
@@ -47,6 +49,7 @@ const SallaProductCarousel: FC<Props> = ({
|
|
|
47
49
|
totalSlides,
|
|
48
50
|
}) => {
|
|
49
51
|
if (!totalSlides || totalSlides === 0) return null;
|
|
52
|
+
const [openTryOn, setOpenTryOn] = useState<string | null>(null);
|
|
50
53
|
|
|
51
54
|
return (
|
|
52
55
|
<section
|
|
@@ -69,7 +72,7 @@ const SallaProductCarousel: FC<Props> = ({
|
|
|
69
72
|
|
|
70
73
|
if (!currentVariant) return null;
|
|
71
74
|
|
|
72
|
-
const count = counts[currentVariant.variant_id] || 0;
|
|
75
|
+
// const count = counts[currentVariant.variant_id] || 0;
|
|
73
76
|
|
|
74
77
|
// Build unique, cleaned list of options (exclude empty/'none')
|
|
75
78
|
const options = Array.from(
|
|
@@ -91,27 +94,30 @@ const SallaProductCarousel: FC<Props> = ({
|
|
|
91
94
|
}
|
|
92
95
|
};
|
|
93
96
|
|
|
94
|
-
const increment = async () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const decrement = async () => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
97
|
+
// const increment = async () => {
|
|
98
|
+
// setCounts(prev => ({
|
|
99
|
+
// ...prev,
|
|
100
|
+
// [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
|
|
101
|
+
// }));
|
|
102
|
+
// await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
|
|
103
|
+
// };
|
|
104
|
+
|
|
105
|
+
// const decrement = async () => {
|
|
106
|
+
// if (count === 0) return;
|
|
107
|
+
// setCounts(prev => ({
|
|
108
|
+
// ...prev,
|
|
109
|
+
// [currentVariant.variant_id]: Math.max(
|
|
110
|
+
// 0,
|
|
111
|
+
// (prev[currentVariant.variant_id] || 0) - 1
|
|
112
|
+
// ),
|
|
113
|
+
// }));
|
|
114
|
+
// await handleRemoveFromCart(currentVariant.variant_id, 1);
|
|
115
|
+
// };
|
|
113
116
|
|
|
114
117
|
// Ensure the select has a stable value (fallback to first option)
|
|
118
|
+
|
|
119
|
+
const isTryOnOpen = openTryOn === currentVariant.variant_id;
|
|
120
|
+
|
|
115
121
|
const selectValue = (currentVariant.option ?? options[0] ?? '');
|
|
116
122
|
|
|
117
123
|
return (
|
|
@@ -174,8 +180,17 @@ const SallaProductCarousel: FC<Props> = ({
|
|
|
174
180
|
</div>
|
|
175
181
|
)}
|
|
176
182
|
|
|
177
|
-
|
|
178
|
-
<
|
|
183
|
+
|
|
184
|
+
<Button
|
|
185
|
+
style={{ backgroundColor: colorCode, color: "white" }}
|
|
186
|
+
className="px-6 py-2 rounded-lg shadow-md transition"
|
|
187
|
+
onClick={() =>
|
|
188
|
+
setOpenTryOn(isTryOnOpen ? null : currentVariant.variant_id)
|
|
189
|
+
}
|
|
190
|
+
>
|
|
191
|
+
{isTryOnOpen ? "Close Try On" : "Try On"}
|
|
192
|
+
</Button>
|
|
193
|
+
{/* <button
|
|
179
194
|
type="button"
|
|
180
195
|
className="sallaX-qty sallaX-qty--minus"
|
|
181
196
|
onClick={decrement}
|
|
@@ -194,8 +209,14 @@ const SallaProductCarousel: FC<Props> = ({
|
|
|
194
209
|
aria-label="Increase quantity"
|
|
195
210
|
>
|
|
196
211
|
+
|
|
197
|
-
</button>
|
|
198
|
-
|
|
212
|
+
</button> */}
|
|
213
|
+
|
|
214
|
+
{ isTryOnOpen && (
|
|
215
|
+
<div className="mt-4">
|
|
216
|
+
<TryOnBox productID={currentVariant.variant_id} productImage={currentVariant.image} />
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
|
|
199
220
|
</div>
|
|
200
221
|
</article>
|
|
201
222
|
);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React,{useState} from 'react';
|
|
2
2
|
import type { FC } from 'react';
|
|
3
3
|
import 'keen-slider/keen-slider.min.css';
|
|
4
4
|
import type { KeenSliderInstance } from 'keen-slider/react';
|
|
5
|
+
import { Button } from 'antd';
|
|
5
6
|
import './SallaSwiper.css';
|
|
7
|
+
import TryOnBox from "./TryOn";
|
|
6
8
|
|
|
7
9
|
interface ProductVariant {
|
|
8
10
|
variant_id: string;
|
|
@@ -31,14 +33,16 @@ interface Props {
|
|
|
31
33
|
totalSlides: number;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
|
|
37
|
+
|
|
34
38
|
const ZidProductCarousel: FC<Props> = ({
|
|
35
39
|
groupedProducts,
|
|
36
40
|
selectedVariants,
|
|
37
41
|
setSelectedVariants,
|
|
38
|
-
counts,
|
|
39
|
-
setCounts,
|
|
40
|
-
handleAddToCart,
|
|
41
|
-
handleRemoveFromCart,
|
|
42
|
+
// counts,
|
|
43
|
+
// setCounts,
|
|
44
|
+
// handleAddToCart,
|
|
45
|
+
// handleRemoveFromCart,
|
|
42
46
|
colorCode = '#7F28F8',
|
|
43
47
|
shortenName,
|
|
44
48
|
sliderRef,
|
|
@@ -46,6 +50,9 @@ const ZidProductCarousel: FC<Props> = ({
|
|
|
46
50
|
// currentSlide, // currently unused
|
|
47
51
|
totalSlides,
|
|
48
52
|
}) => {
|
|
53
|
+
|
|
54
|
+
const [openTryOn, setOpenTryOn] = useState<string | null>(null);
|
|
55
|
+
|
|
49
56
|
if (!totalSlides || totalSlides === 0) return null;
|
|
50
57
|
|
|
51
58
|
return (
|
|
@@ -69,7 +76,7 @@ const ZidProductCarousel: FC<Props> = ({
|
|
|
69
76
|
|
|
70
77
|
if (!currentVariant) return null;
|
|
71
78
|
|
|
72
|
-
const count = counts[currentVariant.variant_id] || 0;
|
|
79
|
+
// const count = counts[currentVariant.variant_id] || 0;
|
|
73
80
|
|
|
74
81
|
// Build unique, cleaned list of options (exclude empty/'none')
|
|
75
82
|
const options = Array.from(
|
|
@@ -91,25 +98,27 @@ const ZidProductCarousel: FC<Props> = ({
|
|
|
91
98
|
}
|
|
92
99
|
};
|
|
93
100
|
|
|
94
|
-
const increment = async () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const decrement = async () => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
101
|
+
// const increment = async () => {
|
|
102
|
+
// setCounts(prev => ({
|
|
103
|
+
// ...prev,
|
|
104
|
+
// [currentVariant.variant_id]: (prev[currentVariant.variant_id] || 0) + 1,
|
|
105
|
+
// }));
|
|
106
|
+
// await handleAddToCart(currentVariant.variant_id, currentVariant.name, 1);
|
|
107
|
+
// };
|
|
108
|
+
|
|
109
|
+
// const decrement = async () => {
|
|
110
|
+
// if (count === 0) return;
|
|
111
|
+
// setCounts(prev => ({
|
|
112
|
+
// ...prev,
|
|
113
|
+
// [currentVariant.variant_id]: Math.max(
|
|
114
|
+
// 0,
|
|
115
|
+
// (prev[currentVariant.variant_id] || 0) - 1
|
|
116
|
+
// ),
|
|
117
|
+
// }));
|
|
118
|
+
// await handleRemoveFromCart(currentVariant.variant_id, 1);
|
|
119
|
+
// };
|
|
120
|
+
|
|
121
|
+
const isTryOnOpen = openTryOn === currentVariant.variant_id;
|
|
113
122
|
|
|
114
123
|
// Ensure the select has a stable value (fallback to first option)
|
|
115
124
|
const selectValue = (currentVariant.option ?? options[0] ?? '');
|
|
@@ -174,7 +183,20 @@ const ZidProductCarousel: FC<Props> = ({
|
|
|
174
183
|
</div>
|
|
175
184
|
)}
|
|
176
185
|
|
|
177
|
-
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
<Button
|
|
189
|
+
style={{ backgroundColor: colorCode, color: "white" }}
|
|
190
|
+
className="px-6 py-2 rounded-lg shadow-md transition"
|
|
191
|
+
onClick={() =>
|
|
192
|
+
setOpenTryOn(isTryOnOpen ? null : currentVariant.variant_id)
|
|
193
|
+
}
|
|
194
|
+
>
|
|
195
|
+
{isTryOnOpen ? "Close Try On" : "Try On"}
|
|
196
|
+
</Button>
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
{/* <div className="sallaX-cta" aria-label="Quantity controls">
|
|
178
200
|
<button
|
|
179
201
|
type="button"
|
|
180
202
|
className="sallaX-qty sallaX-qty--minus"
|
|
@@ -195,7 +217,13 @@ const ZidProductCarousel: FC<Props> = ({
|
|
|
195
217
|
>
|
|
196
218
|
+
|
|
197
219
|
</button>
|
|
198
|
-
</div>
|
|
220
|
+
</div> */}
|
|
221
|
+
{ isTryOnOpen && (
|
|
222
|
+
<div className="mt-4">
|
|
223
|
+
<TryOnBox productID={currentVariant.variant_id} productImage={currentVariant.image} />
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
|
|
199
227
|
</div>
|
|
200
228
|
</article>
|
|
201
229
|
);
|
|
@@ -1,22 +1,50 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Button, Spin } from 'antd';
|
|
3
|
-
import { LoadingOutlined, CloudUploadOutlined } from '@ant-design/icons';
|
|
1
|
+
import React, { useState,useEffect } from 'react';
|
|
2
|
+
import { Button, Spin, Alert } from 'antd';
|
|
3
|
+
import { LoadingOutlined, CloudUploadOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
4
4
|
import config from '../../../../hooks/config';
|
|
5
5
|
|
|
6
6
|
interface TryOnProps {
|
|
7
7
|
productImage: string; // product variant image URL
|
|
8
|
-
productID:string;
|
|
8
|
+
productID: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const sweetMessages = [
|
|
12
|
+
"You look amazing in this! ✨",
|
|
13
|
+
"Wow, this really suits your style 💜",
|
|
14
|
+
"Looking sharp — that’s a vibe 😎",
|
|
15
|
+
"Perfect fit! This could be your new favorite 👌",
|
|
16
|
+
"You’re rocking this look 💯",
|
|
17
|
+
"That outfit has your name written all over it 💃",
|
|
18
|
+
"🔥 Stunning choice! You wear it so well.",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const loadingMessages = [
|
|
22
|
+
"✨ Analyzing your style...",
|
|
23
|
+
"🧵 Matching the product perfectly...",
|
|
24
|
+
"🎨 Adjusting colors and lighting...",
|
|
25
|
+
"👗 Styling you up...",
|
|
26
|
+
"💎 Almost ready, polishing the details..."
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// cycle messages while loading
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const TryOnBox: React.FC<TryOnProps> = ({ productImage, productID }) => {
|
|
12
35
|
const [userImage, setUserImage] = useState<string | null>(null);
|
|
13
36
|
const [resultImage, setResultImage] = useState<string | null>(null);
|
|
14
37
|
const [loading, setLoading] = useState(false);
|
|
38
|
+
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
|
39
|
+
const [sweetMessage, setSweetMessage] = useState<string | null>(null);
|
|
40
|
+
const [loadingIndex, setLoadingIndex] = useState(0);
|
|
15
41
|
|
|
16
|
-
// Call
|
|
42
|
+
// Call Django Ark API
|
|
17
43
|
const sendToArk = async (file: File) => {
|
|
18
44
|
setLoading(true);
|
|
19
45
|
setResultImage(null);
|
|
46
|
+
setMessage(null);
|
|
47
|
+
setSweetMessage(null);
|
|
20
48
|
|
|
21
49
|
const reader = new FileReader();
|
|
22
50
|
reader.onload = async () => {
|
|
@@ -27,22 +55,30 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
|
|
|
27
55
|
method: 'POST',
|
|
28
56
|
headers: { 'Content-Type': 'application/json' },
|
|
29
57
|
body: JSON.stringify({
|
|
30
|
-
userImageUrl: base64UserImage,
|
|
31
|
-
productImageUrl: productImage,
|
|
32
|
-
productId:productID,
|
|
33
|
-
username:localStorage.getItem('company_username')
|
|
58
|
+
userImageUrl: base64UserImage,
|
|
59
|
+
productImageUrl: productImage,
|
|
60
|
+
productId: productID,
|
|
61
|
+
username: localStorage.getItem('company_username'),
|
|
34
62
|
}),
|
|
35
63
|
});
|
|
36
64
|
|
|
37
65
|
const data = await response.json();
|
|
38
66
|
|
|
39
|
-
if (data.resultImage) {
|
|
40
|
-
setResultImage(data.resultImage);
|
|
67
|
+
if (response.ok && data.resultImage) {
|
|
68
|
+
setResultImage(data.resultImage);
|
|
69
|
+
|
|
70
|
+
// Pick a random sweet message
|
|
71
|
+
const randomMsg = sweetMessages[Math.floor(Math.random() * sweetMessages.length)];
|
|
72
|
+
setSweetMessage(randomMsg);
|
|
41
73
|
} else {
|
|
42
|
-
|
|
74
|
+
setMessage({
|
|
75
|
+
type: 'error',
|
|
76
|
+
text: data.error || 'Something went wrong while generating the try-on.',
|
|
77
|
+
});
|
|
43
78
|
}
|
|
44
79
|
} catch (err) {
|
|
45
80
|
console.error('Error calling Ark API:', err);
|
|
81
|
+
setMessage({ type: 'error', text: 'Failed to reach Try-On service. Please try again.' });
|
|
46
82
|
} finally {
|
|
47
83
|
setLoading(false);
|
|
48
84
|
}
|
|
@@ -57,13 +93,39 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
|
|
|
57
93
|
}
|
|
58
94
|
};
|
|
59
95
|
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (loading) {
|
|
98
|
+
setLoadingIndex(0);
|
|
99
|
+
const interval = setInterval(() => {
|
|
100
|
+
setLoadingIndex((prev) => {
|
|
101
|
+
if (prev < loadingMessages.length - 1) {
|
|
102
|
+
return prev + 1; // move forward
|
|
103
|
+
}
|
|
104
|
+
return prev; // stay at the last message
|
|
105
|
+
});
|
|
106
|
+
}, 1800); // change every 1.8s
|
|
107
|
+
return () => clearInterval(interval);
|
|
108
|
+
}
|
|
109
|
+
}, [loading]);
|
|
110
|
+
|
|
60
111
|
return (
|
|
61
|
-
<div className="mt-4 border rounded-
|
|
62
|
-
{/*
|
|
112
|
+
<div className="mt-4 border rounded-xl p-6 bg-white shadow-sm">
|
|
113
|
+
{/* Messages */}
|
|
114
|
+
{message && (
|
|
115
|
+
<Alert
|
|
116
|
+
type={message.type}
|
|
117
|
+
message={message.text}
|
|
118
|
+
showIcon
|
|
119
|
+
closable
|
|
120
|
+
className="mb-4"
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
{/* Upload Box */}
|
|
63
125
|
{!userImage && !resultImage && !loading && (
|
|
64
|
-
<label className="flex flex-col items-center justify-center border-2 border-dashed border-gray-
|
|
65
|
-
<CloudUploadOutlined style={{ fontSize:
|
|
66
|
-
<span className="text-gray-500">Click
|
|
126
|
+
<label className="flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl w-full h-44 cursor-pointer hover:border-purple-500 hover:bg-purple-50 transition">
|
|
127
|
+
<CloudUploadOutlined style={{ fontSize: 34, marginBottom: 8, color: '#7F28F8' }} />
|
|
128
|
+
<span className="text-gray-500 font-medium">Click to upload a picture</span>
|
|
67
129
|
<input
|
|
68
130
|
type="file"
|
|
69
131
|
accept="image/*"
|
|
@@ -75,29 +137,37 @@ const TryOnBox: React.FC<TryOnProps> = ({ productImage,productID }) => {
|
|
|
75
137
|
|
|
76
138
|
{/* Loader */}
|
|
77
139
|
{loading && (
|
|
78
|
-
<div className="flex flex-col items-center justify-center h-
|
|
79
|
-
<Spin indicator={<LoadingOutlined style={{ fontSize:
|
|
80
|
-
<p className="mt-2 text-gray-
|
|
140
|
+
<div className="flex flex-col items-center justify-center h-44 text-center">
|
|
141
|
+
<Spin indicator={<LoadingOutlined style={{ fontSize: 36, color: '#7F28F8' }} spin />} />
|
|
142
|
+
<p className="mt-2 text-gray-700 font-medium">{loadingMessages[loadingIndex]}</p>
|
|
81
143
|
</div>
|
|
82
144
|
)}
|
|
83
145
|
|
|
146
|
+
|
|
147
|
+
|
|
84
148
|
{/* Result Preview */}
|
|
85
149
|
{!loading && resultImage && (
|
|
86
|
-
<div className="flex flex-col items-center">
|
|
87
|
-
<p className="mb-2 text-sm font-
|
|
150
|
+
<div className="flex flex-col items-center text-center">
|
|
151
|
+
{/* <p className="mb-2 text-sm font-semibold text-gray-700">✨ Try-On Result</p> */}
|
|
88
152
|
<img
|
|
89
153
|
src={resultImage}
|
|
90
154
|
alt="Result"
|
|
91
|
-
className="max-h-
|
|
155
|
+
className="max-h-72 object-contain rounded-lg border shadow-sm"
|
|
92
156
|
/>
|
|
93
157
|
|
|
158
|
+
{/* Sweet Message */}
|
|
159
|
+
{sweetMessage && (
|
|
160
|
+
<p className="mt-3 text-purple-600 font-medium italic">{sweetMessage}</p>
|
|
161
|
+
)}
|
|
162
|
+
|
|
94
163
|
<Button
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
className="mt-3"
|
|
164
|
+
icon={<ReloadOutlined />}
|
|
165
|
+
style={{ marginTop: '12px' }}
|
|
98
166
|
onClick={() => {
|
|
99
167
|
setUserImage(null);
|
|
100
168
|
setResultImage(null);
|
|
169
|
+
setMessage(null);
|
|
170
|
+
setSweetMessage(null);
|
|
101
171
|
}}
|
|
102
172
|
>
|
|
103
173
|
Try Another
|
|
@@ -26,6 +26,7 @@ interface Message {
|
|
|
26
26
|
audio_url?:string;
|
|
27
27
|
isConnect?:boolean;
|
|
28
28
|
calendly?:boolean;
|
|
29
|
+
isBookings?:boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
interface ChatMessagesProps {
|
|
@@ -123,6 +124,20 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
|
|
|
123
124
|
)
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
if (msg.isBookings){
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
key={i}
|
|
131
|
+
style={{
|
|
132
|
+
borderRadius: 5,
|
|
133
|
+
color: "white",
|
|
134
|
+
width: "100%",
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<CalendlyEmbeds calendlyLink={calendly} />
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
126
141
|
|
|
127
142
|
|
|
128
143
|
|
|
@@ -147,20 +162,7 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
|
|
|
147
162
|
);
|
|
148
163
|
}
|
|
149
164
|
|
|
150
|
-
|
|
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
|
-
}
|
|
165
|
+
|
|
164
166
|
|
|
165
167
|
|
|
166
168
|
|
|
@@ -83,23 +83,33 @@ export const useChatbotAPI = (params: Params) => {
|
|
|
83
83
|
const data = await res.json();
|
|
84
84
|
|
|
85
85
|
if (data.message_history) {
|
|
86
|
-
const assistantMessage = data.isConnect
|
|
87
|
-
? {
|
|
88
|
-
role: 'assistant',
|
|
89
|
-
content: 'Your support request is on its way—please stand by!',
|
|
90
|
-
isConnect: true,
|
|
91
|
-
sender: 'Ai Agent',
|
|
92
|
-
isBusiness: false,
|
|
93
|
-
isHtml: data.isHtml,
|
|
94
|
-
}
|
|
95
|
-
: {
|
|
96
|
-
role: 'assistant',
|
|
97
|
-
content: data.message,
|
|
98
|
-
sender: 'Ai Agent',
|
|
99
|
-
isBusiness: false,
|
|
100
|
-
isHtml: data.isHtml,
|
|
101
|
-
};
|
|
102
86
|
|
|
87
|
+
const assistantMessage = data.isConnect
|
|
88
|
+
? {
|
|
89
|
+
role: 'assistant',
|
|
90
|
+
content: data.message,
|
|
91
|
+
isConnect: true,
|
|
92
|
+
sender: 'Ai Agent',
|
|
93
|
+
isBusiness: false,
|
|
94
|
+
isHtml: data.isHtml,
|
|
95
|
+
}
|
|
96
|
+
: data.isBookings
|
|
97
|
+
? {
|
|
98
|
+
role: 'assistant',
|
|
99
|
+
content: data.message,
|
|
100
|
+
isBookings: true,
|
|
101
|
+
sender: 'Ai Agent',
|
|
102
|
+
isBusiness: false,
|
|
103
|
+
isHtml: data.isHtml,
|
|
104
|
+
}
|
|
105
|
+
: {
|
|
106
|
+
role: 'assistant',
|
|
107
|
+
content: data.message,
|
|
108
|
+
sender: 'Ai Agent',
|
|
109
|
+
isBusiness: false,
|
|
110
|
+
isHtml: data.isHtml,
|
|
111
|
+
};
|
|
112
|
+
|
|
103
113
|
// Send message through WebSocket if available
|
|
104
114
|
if (socket?.readyState === WebSocket.OPEN) {
|
|
105
115
|
socket.send(
|
|
@@ -121,7 +131,6 @@ export const useChatbotAPI = (params: Params) => {
|
|
|
121
131
|
const filtered = prev.filter((msg) => !msg.typing);
|
|
122
132
|
const updated = [...filtered, assistantMessage];
|
|
123
133
|
|
|
124
|
-
console.log(calendlyLink)
|
|
125
134
|
// Add Calendly message if applicable
|
|
126
135
|
if (
|
|
127
136
|
data.message &&
|