shopify-chatbot-widget 0.8.2 β 0.8.4
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 +147 -145
- package/package.json +1 -1
- package/src/Chatbot/components/Messages/Carousel/CardSwiper.tsx +4 -8
- package/src/Chatbot/components/Messages/Carousel/TryOn.tsx +97 -27
- package/src/Chatbot/components/Messages/ChatMessages.tsx +16 -14
- package/src/hooks/config.tsx +2 -2
- package/src/hooks/useChatbotAPI.tsx +26 -17
- package/src/types/chatbot.ts +1 -0
package/package.json
CHANGED
|
@@ -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,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
|
|
package/src/hooks/config.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
const config = {
|
|
4
|
-
apiUrl:'https://
|
|
5
|
-
websocketUrl:'wss://
|
|
4
|
+
apiUrl:'https://jawebcrm.onrender.com/api/',
|
|
5
|
+
websocketUrl:'wss://jawebcrm.onrender.com/ws' ,
|
|
6
6
|
|
|
7
7
|
// apiUrl:'http://localhost:8000/api/',
|
|
8
8
|
// websocketUrl:'ws://localhost:8000/ws',
|
|
@@ -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 &&
|