@shop-prompter/react 1.1.1 → 1.2.1
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/CHANGELOG.md +12 -0
- package/dist/index.js +149 -46
- package/dist/index.mjs +149 -46
- package/package.json +1 -1
- package/src/product-card-list.component.jsx +18 -7
- package/src/shop-prompter.component.jsx +130 -77
- package/src/translations.ts +53 -0
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import { marked } from "marked";
|
|
5
5
|
import { ShopPrompterService } from "./shop-prompter.service";
|
|
6
|
+
import { getTranslations, DEFAULT_LOCALE } from "./translations";
|
|
6
7
|
import ThumbsUpIcon from "./thumbs-up-icon.component";
|
|
7
8
|
import ThumbsDownIcon from "./thumbs-down-icon.component";
|
|
8
9
|
import ProductCardList from "./product-card-list.component";
|
|
@@ -41,7 +42,7 @@ const styles = {
|
|
|
41
42
|
},
|
|
42
43
|
button: {
|
|
43
44
|
padding: "0.625rem 1rem",
|
|
44
|
-
border: "
|
|
45
|
+
border: "2px solid #e5e7eb",
|
|
45
46
|
borderRadius: "0.75rem",
|
|
46
47
|
backgroundColor: "transparent",
|
|
47
48
|
transition: "background-color 0.2s, color 0.2s",
|
|
@@ -84,15 +85,16 @@ const styles = {
|
|
|
84
85
|
fontFamily: 'var(--font-space-grotesk), "Space Grotesk", sans-serif',
|
|
85
86
|
fontWeight: 500,
|
|
86
87
|
fontStyle: "Medium",
|
|
87
|
-
fontSize: "
|
|
88
|
+
fontSize: "28px",
|
|
88
89
|
leadingTrim: "NONE",
|
|
89
90
|
lineHeight: "100%",
|
|
90
91
|
letterSpacing: "0%",
|
|
92
|
+
paddingBottom: "1rem",
|
|
91
93
|
textAlign: "center",
|
|
92
94
|
},
|
|
93
95
|
welcomeDescription: {
|
|
94
96
|
color: "#4b5563",
|
|
95
|
-
maxWidth: "
|
|
97
|
+
maxWidth: "32rem",
|
|
96
98
|
fontFamily: 'var(--font-space-grotesk), "Space Grotesk", sans-serif',
|
|
97
99
|
fontWeight: 400,
|
|
98
100
|
fontStyle: "Regular",
|
|
@@ -107,7 +109,7 @@ const styles = {
|
|
|
107
109
|
display: "flex",
|
|
108
110
|
flexWrap: "wrap",
|
|
109
111
|
justifyContent: "center",
|
|
110
|
-
gap: "
|
|
112
|
+
gap: "1rem",
|
|
111
113
|
},
|
|
112
114
|
messageContainerUser: {
|
|
113
115
|
display: "flex",
|
|
@@ -252,6 +254,13 @@ const styles = {
|
|
|
252
254
|
textAlign: "center",
|
|
253
255
|
marginTop: "0.75rem",
|
|
254
256
|
},
|
|
257
|
+
predefinedPromptDisclaimer: {
|
|
258
|
+
fontSize: "0.75rem",
|
|
259
|
+
color: "#6b7280",
|
|
260
|
+
textAlign: "center",
|
|
261
|
+
marginTop: "2rem",
|
|
262
|
+
width: "100%",
|
|
263
|
+
},
|
|
255
264
|
backdrop: {
|
|
256
265
|
position: "fixed",
|
|
257
266
|
top: 0,
|
|
@@ -407,16 +416,6 @@ const ANIMATION_STYLES = `
|
|
|
407
416
|
border-color: #d1d5db !important;
|
|
408
417
|
}
|
|
409
418
|
`;
|
|
410
|
-
const TRANSLATIONS = {
|
|
411
|
-
newChat: "New Chat",
|
|
412
|
-
welcomeToShopPrompter: "Welcome to ShopPrompter",
|
|
413
|
-
welcomeDescription:
|
|
414
|
-
"I am your personal AI shopping assistant. Ask me anything about our products!",
|
|
415
|
-
askMeAnything: "Ask me anything...",
|
|
416
|
-
thinking: "Thinking...",
|
|
417
|
-
feedbackError:
|
|
418
|
-
"We are sorry, but we could not process your feedback. Please try again later.",
|
|
419
|
-
};
|
|
420
419
|
const ICONS = {
|
|
421
420
|
chat: "https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/icons/message-square.svg",
|
|
422
421
|
shopprompter:
|
|
@@ -454,6 +453,23 @@ function ShopPrompter(props) {
|
|
|
454
453
|
() => ""
|
|
455
454
|
);
|
|
456
455
|
|
|
456
|
+
function t() {
|
|
457
|
+
return getTranslations(props.defaultLocale);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function requireClickOnPredefinedPromptVal() {
|
|
461
|
+
const flag = props.requireClickOnPredefinedPrompt;
|
|
462
|
+
return typeof flag === "string" ? flag !== "false" : flag ?? true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function shouldHideInput() {
|
|
466
|
+
const requireClick = requireClickOnPredefinedPromptVal();
|
|
467
|
+
if (!requireClick) return false;
|
|
468
|
+
if (chatMessages.length > 0) return false;
|
|
469
|
+
if (prompts().length === 0) return false;
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
457
473
|
function hasToken() {
|
|
458
474
|
const token =
|
|
459
475
|
props.shopPrompterToken || apiConfigObj?.()?.shopPrompterToken;
|
|
@@ -635,7 +651,13 @@ function ShopPrompter(props) {
|
|
|
635
651
|
setIsLoading(loading);
|
|
636
652
|
},
|
|
637
653
|
onError: (err) => {
|
|
638
|
-
|
|
654
|
+
if (err.includes("429")) {
|
|
655
|
+
setError(t().guestLimitReached);
|
|
656
|
+
} else if (err.includes("401") || err.includes("403")) {
|
|
657
|
+
setError(t().sessionExpired);
|
|
658
|
+
} else {
|
|
659
|
+
setError(err);
|
|
660
|
+
}
|
|
639
661
|
},
|
|
640
662
|
onCartOperation: (ops) => {
|
|
641
663
|
if (props.onCartOperation) {
|
|
@@ -794,6 +816,10 @@ function ShopPrompter(props) {
|
|
|
794
816
|
return;
|
|
795
817
|
}
|
|
796
818
|
const response = await service?.getAgentExperience();
|
|
819
|
+
if (!response) {
|
|
820
|
+
setError(t().agentExperienceError);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
797
823
|
setAgentExperience(response);
|
|
798
824
|
})();
|
|
799
825
|
}
|
|
@@ -824,7 +850,7 @@ function ShopPrompter(props) {
|
|
|
824
850
|
.catch((err) => {
|
|
825
851
|
// Revert optimistic update
|
|
826
852
|
setChatMessages(oldMessages);
|
|
827
|
-
setError(err?.message ||
|
|
853
|
+
setError(err?.message || t().feedbackError);
|
|
828
854
|
});
|
|
829
855
|
}
|
|
830
856
|
|
|
@@ -996,9 +1022,7 @@ function ShopPrompter(props) {
|
|
|
996
1022
|
</>
|
|
997
1023
|
)}
|
|
998
1024
|
{!hasToken() && apiConfigObj?.()?.apiKey ? (
|
|
999
|
-
<div style={styles.warningBanner}>
|
|
1000
|
-
API Key is only for development purposes
|
|
1001
|
-
</div>
|
|
1025
|
+
<div style={styles.warningBanner}>{t().apiKeyWarning}</div>
|
|
1002
1026
|
) : null}
|
|
1003
1027
|
<div style={styles.subHeaderContainer}>
|
|
1004
1028
|
<button
|
|
@@ -1006,7 +1030,7 @@ function ShopPrompter(props) {
|
|
|
1006
1030
|
onClick={(e) => handleNewChat()}
|
|
1007
1031
|
style={styles.headerButton}
|
|
1008
1032
|
>
|
|
1009
|
-
{
|
|
1033
|
+
{t().newChat}
|
|
1010
1034
|
</button>
|
|
1011
1035
|
{props.chatPosition === "classic-chatbot" ||
|
|
1012
1036
|
props.chatPosition === "right-panel" ||
|
|
@@ -1046,7 +1070,7 @@ function ShopPrompter(props) {
|
|
|
1046
1070
|
fontFamily: themeObj().fontFamily || undefined,
|
|
1047
1071
|
}}
|
|
1048
1072
|
>
|
|
1049
|
-
|
|
1073
|
+
{t().configErrorTitle}
|
|
1050
1074
|
</h3>
|
|
1051
1075
|
<p
|
|
1052
1076
|
style={{
|
|
@@ -1054,8 +1078,7 @@ function ShopPrompter(props) {
|
|
|
1054
1078
|
fontFamily: themeObj().fontFamily || undefined,
|
|
1055
1079
|
}}
|
|
1056
1080
|
>
|
|
1057
|
-
|
|
1058
|
-
Please check your setup.
|
|
1081
|
+
{t().configErrorMessage}
|
|
1059
1082
|
</p>
|
|
1060
1083
|
<div
|
|
1061
1084
|
style={{
|
|
@@ -1069,7 +1092,7 @@ function ShopPrompter(props) {
|
|
|
1069
1092
|
marginBottom: "0.25rem",
|
|
1070
1093
|
}}
|
|
1071
1094
|
>
|
|
1072
|
-
|
|
1095
|
+
{t().configMissingParams}
|
|
1073
1096
|
</div>
|
|
1074
1097
|
<div
|
|
1075
1098
|
style={{
|
|
@@ -1085,14 +1108,14 @@ function ShopPrompter(props) {
|
|
|
1085
1108
|
color: "#ef4444",
|
|
1086
1109
|
}}
|
|
1087
1110
|
>
|
|
1088
|
-
|
|
1111
|
+
{t().configMissingToken}
|
|
1089
1112
|
</span>
|
|
1090
1113
|
<span
|
|
1091
1114
|
style={{
|
|
1092
1115
|
color: "#ef4444",
|
|
1093
1116
|
}}
|
|
1094
1117
|
>
|
|
1095
|
-
|
|
1118
|
+
{t().configMissingApiKey}
|
|
1096
1119
|
</span>
|
|
1097
1120
|
</div>
|
|
1098
1121
|
</div>
|
|
@@ -1126,8 +1149,8 @@ function ShopPrompter(props) {
|
|
|
1126
1149
|
: ICONS.shopprompter
|
|
1127
1150
|
}
|
|
1128
1151
|
style={{
|
|
1129
|
-
width: "
|
|
1130
|
-
height: "
|
|
1152
|
+
width: "96px",
|
|
1153
|
+
height: "96px",
|
|
1131
1154
|
marginBottom: "24px",
|
|
1132
1155
|
borderRadius: "12px",
|
|
1133
1156
|
objectFit: "cover",
|
|
@@ -1140,10 +1163,12 @@ function ShopPrompter(props) {
|
|
|
1140
1163
|
themeObj().fontFamily ||
|
|
1141
1164
|
styles.welcomeTitle.fontFamily,
|
|
1142
1165
|
}}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1166
|
+
dangerouslySetInnerHTML={{
|
|
1167
|
+
__html:
|
|
1168
|
+
agentExperience?.greeting?.title ||
|
|
1169
|
+
t().welcomeToShopPrompter,
|
|
1170
|
+
}}
|
|
1171
|
+
/>
|
|
1147
1172
|
<p
|
|
1148
1173
|
style={{
|
|
1149
1174
|
...styles.welcomeDescription,
|
|
@@ -1151,15 +1176,21 @@ function ShopPrompter(props) {
|
|
|
1151
1176
|
themeObj().fontFamily ||
|
|
1152
1177
|
styles.welcomeDescription.fontFamily,
|
|
1153
1178
|
}}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1179
|
+
dangerouslySetInnerHTML={{
|
|
1180
|
+
__html:
|
|
1181
|
+
agentExperience?.greeting?.message ||
|
|
1182
|
+
t().welcomeDescription,
|
|
1183
|
+
}}
|
|
1184
|
+
/>
|
|
1158
1185
|
<div style={styles.buttonContainer}>
|
|
1159
1186
|
{prompts()?.map((item) => (
|
|
1160
1187
|
<button
|
|
1161
1188
|
key={item.prompt}
|
|
1162
|
-
style={
|
|
1189
|
+
style={{
|
|
1190
|
+
...styles.button,
|
|
1191
|
+
borderColor: themeObj().actionColor || "#e5e7eb",
|
|
1192
|
+
outlineColor: themeObj().actionColor || undefined,
|
|
1193
|
+
}}
|
|
1163
1194
|
onClick={(event) => {
|
|
1164
1195
|
setInput(item.prompt);
|
|
1165
1196
|
handleSend(item.prompt);
|
|
@@ -1169,6 +1200,17 @@ function ShopPrompter(props) {
|
|
|
1169
1200
|
</button>
|
|
1170
1201
|
))}
|
|
1171
1202
|
</div>
|
|
1203
|
+
{requireClickOnPredefinedPromptVal() &&
|
|
1204
|
+
prompts().length > 0 ? (
|
|
1205
|
+
<div
|
|
1206
|
+
style={{
|
|
1207
|
+
...styles.predefinedPromptDisclaimer,
|
|
1208
|
+
fontFamily: themeObj().fontFamily || undefined,
|
|
1209
|
+
}}
|
|
1210
|
+
>
|
|
1211
|
+
{t().predefinedPromptDisclaimer}
|
|
1212
|
+
</div>
|
|
1213
|
+
) : null}
|
|
1172
1214
|
</div>
|
|
1173
1215
|
) : null}
|
|
1174
1216
|
{!showWelcome() ? (
|
|
@@ -1307,7 +1349,9 @@ function ShopPrompter(props) {
|
|
|
1307
1349
|
{getShowProducts(message) ? (
|
|
1308
1350
|
<ProductCardList
|
|
1309
1351
|
skus={message.productSkus}
|
|
1310
|
-
defaultLocale={
|
|
1352
|
+
defaultLocale={
|
|
1353
|
+
props.defaultLocale || DEFAULT_LOCALE
|
|
1354
|
+
}
|
|
1311
1355
|
showPriceIfEmpty={showPriceIfEmptyVal()}
|
|
1312
1356
|
visibleFields={visibleFieldsList()}
|
|
1313
1357
|
/>
|
|
@@ -1319,50 +1363,59 @@ function ShopPrompter(props) {
|
|
|
1319
1363
|
</div>
|
|
1320
1364
|
) : null}
|
|
1321
1365
|
{error ? (
|
|
1322
|
-
<div style={styles.errorContainer}>
|
|
1366
|
+
<div style={styles.errorContainer}>{error}</div>
|
|
1323
1367
|
) : null}
|
|
1324
1368
|
</div>
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
<img
|
|
1356
|
-
alt="Send"
|
|
1357
|
-
src={ICONS.sendHorizontal}
|
|
1369
|
+
{!shouldHideInput() ? (
|
|
1370
|
+
<div
|
|
1371
|
+
style={{
|
|
1372
|
+
...styles.inputSection,
|
|
1373
|
+
backgroundColor:
|
|
1374
|
+
themeObj().mainBackgroundColor ||
|
|
1375
|
+
styles.inputSection.backgroundColor,
|
|
1376
|
+
...(error
|
|
1377
|
+
? {
|
|
1378
|
+
opacity: "0.5",
|
|
1379
|
+
pointerEvents: "none",
|
|
1380
|
+
}
|
|
1381
|
+
: {}),
|
|
1382
|
+
}}
|
|
1383
|
+
>
|
|
1384
|
+
<div style={styles.inputWrapper}>
|
|
1385
|
+
<input
|
|
1386
|
+
id="shop-prompter-input"
|
|
1387
|
+
type="text"
|
|
1388
|
+
style={styles.input}
|
|
1389
|
+
placeholder={
|
|
1390
|
+
agentExperience?.hintText || t().askMeAnything
|
|
1391
|
+
}
|
|
1392
|
+
value={input}
|
|
1393
|
+
onInput={(e) => onInputChange(e)}
|
|
1394
|
+
onKeyDown={(e) => onInputKeyDown(e)}
|
|
1395
|
+
disabled={!!error}
|
|
1396
|
+
/>
|
|
1397
|
+
<button
|
|
1398
|
+
onClick={(e) => handleSend()}
|
|
1358
1399
|
style={{
|
|
1359
|
-
|
|
1400
|
+
...styles.formButton,
|
|
1401
|
+
...styles.sendButton,
|
|
1402
|
+
backgroundColor:
|
|
1403
|
+
themeObj().actionColor ||
|
|
1404
|
+
styles.sendButton.backgroundColor,
|
|
1360
1405
|
}}
|
|
1361
|
-
|
|
1362
|
-
|
|
1406
|
+
>
|
|
1407
|
+
<img
|
|
1408
|
+
alt="Send"
|
|
1409
|
+
src={ICONS.sendHorizontal}
|
|
1410
|
+
style={{
|
|
1411
|
+
padding: "4px",
|
|
1412
|
+
}}
|
|
1413
|
+
/>
|
|
1414
|
+
</button>
|
|
1415
|
+
</div>
|
|
1416
|
+
<p style={styles.disclaimer}>{t().disclaimer}</p>
|
|
1363
1417
|
</div>
|
|
1364
|
-
|
|
1365
|
-
</div>
|
|
1418
|
+
) : null}
|
|
1366
1419
|
</div>
|
|
1367
1420
|
) : null}
|
|
1368
1421
|
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const DEFAULT_LOCALE = 'en'
|
|
2
|
+
|
|
3
|
+
export const TRANSLATIONS: Record<string, Record<string, string>> = {
|
|
4
|
+
en: {
|
|
5
|
+
newChat: 'New Chat',
|
|
6
|
+
welcomeToShopPrompter: 'Welcome',
|
|
7
|
+
welcomeDescription:
|
|
8
|
+
'I am your personal AI shopping assistant. Ask me what you would like to know!',
|
|
9
|
+
askMeAnything: 'Ask me anything...',
|
|
10
|
+
thinking: 'Thinking...',
|
|
11
|
+
feedbackError: 'We are sorry, but we could not process your feedback. Please try again later.',
|
|
12
|
+
guestLimitReached: 'We are sorry, the chat is no longer available for a guest user. Please log in to continue chatting.',
|
|
13
|
+
sessionExpired: 'We are sorry. It looks like the session just expired. Please refresh the page to continue chatting.',
|
|
14
|
+
agentExperienceError: 'We are sorry. The session seems to be expired, or the connection is wrongly configured. Please reload the page.',
|
|
15
|
+
apiKeyWarning: 'API Key is only for development purposes',
|
|
16
|
+
configErrorTitle: 'Configuration Error',
|
|
17
|
+
configErrorMessage: 'The initialization requires certain parameters to be present‚. Please check your setup.',
|
|
18
|
+
configMissingParams: 'Missing Parameters:',
|
|
19
|
+
configMissingToken: '- Valid token (not found)',
|
|
20
|
+
configMissingApiKey: '- Valid API Key. (not found)',
|
|
21
|
+
disclaimer: 'AI content may be inaccurate.',
|
|
22
|
+
loadingProducts: 'Loading products...',
|
|
23
|
+
productFetchError: 'We are unable to fetch products.',
|
|
24
|
+
predefinedPromptDisclaimer: 'Click on a prompt, to start the conversation',
|
|
25
|
+
},
|
|
26
|
+
de: {
|
|
27
|
+
newChat: 'Neuer Chat',
|
|
28
|
+
welcomeToShopPrompter: 'Willkommen',
|
|
29
|
+
welcomeDescription:
|
|
30
|
+
'Ich bin Ihr persönlicher KI-Einkaufsassistent. Fragen Sie mich alles über was Sie wissen möchten!',
|
|
31
|
+
askMeAnything: 'Fragen Sie mich etwas...',
|
|
32
|
+
thinking: 'Einen Moment...',
|
|
33
|
+
feedbackError: 'Es tut uns leid, aber wir konnten Ihr Feedback nicht verarbeiten. Bitte versuchen Sie es später erneut.',
|
|
34
|
+
guestLimitReached: 'Es tut uns leid, der Chat steht für Gastbenutzer nicht mehr zur Verfügung. Bitte melden Sie sich an, um weiter zu chatten.',
|
|
35
|
+
sessionExpired: 'Es tut uns leid. Die Sitzung scheint abgelaufen zu sein. Bitte laden Sie die Seite neu, um weiter zu chatten.',
|
|
36
|
+
agentExperienceError: 'Es tut uns leid. Die Sitzung scheint abgelaufen zu sein oder die Verbindung ist falsch konfiguriert. Bitte laden Sie die Seite neu.',
|
|
37
|
+
apiKeyWarning: 'API Key ist nur für Entwicklungszwecke',
|
|
38
|
+
configErrorTitle: 'Konfigurationsfehler',
|
|
39
|
+
configErrorMessage: 'Die Initialsierung benötigt gewisse Parameter, die nicht vorhanden sind. Bitte überprüfen Sie Ihre Einrichtung.',
|
|
40
|
+
configMissingParams: 'Fehlende Parameter:',
|
|
41
|
+
configMissingToken: '- Gültiger Token (nicht gefunden)',
|
|
42
|
+
configMissingApiKey: '- API Key (nicht gefunden)',
|
|
43
|
+
disclaimer: 'KI-Inhalte können Fehler beinhalten.',
|
|
44
|
+
loadingProducts: 'Produkte werden geladen...',
|
|
45
|
+
productFetchError: 'Die Produkte konnten leider nicht geladen werden.',
|
|
46
|
+
predefinedPromptDisclaimer: 'Klicke auf einen Text, um die Unterhaltung zu starten',
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getTranslations(locale?: string): Record<string, string> {
|
|
51
|
+
const resolved = locale || DEFAULT_LOCALE
|
|
52
|
+
return TRANSLATIONS[resolved] || TRANSLATIONS[DEFAULT_LOCALE]
|
|
53
|
+
}
|