@shop-prompter/react 1.1.0 → 1.2.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/CHANGELOG.md +12 -0
- package/dist/index.js +147 -45
- package/dist/index.mjs +147 -45
- package/package.json +1 -1
- package/src/product-card-list.component.jsx +18 -7
- package/src/shop-prompter.component.jsx +128 -76
- 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";
|
|
@@ -84,7 +85,7 @@ 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,7 +93,7 @@ const styles = {
|
|
|
92
93
|
},
|
|
93
94
|
welcomeDescription: {
|
|
94
95
|
color: "#4b5563",
|
|
95
|
-
maxWidth: "
|
|
96
|
+
maxWidth: "32rem",
|
|
96
97
|
fontFamily: 'var(--font-space-grotesk), "Space Grotesk", sans-serif',
|
|
97
98
|
fontWeight: 400,
|
|
98
99
|
fontStyle: "Regular",
|
|
@@ -107,7 +108,7 @@ const styles = {
|
|
|
107
108
|
display: "flex",
|
|
108
109
|
flexWrap: "wrap",
|
|
109
110
|
justifyContent: "center",
|
|
110
|
-
gap: "
|
|
111
|
+
gap: "1rem",
|
|
111
112
|
},
|
|
112
113
|
messageContainerUser: {
|
|
113
114
|
display: "flex",
|
|
@@ -252,6 +253,13 @@ const styles = {
|
|
|
252
253
|
textAlign: "center",
|
|
253
254
|
marginTop: "0.75rem",
|
|
254
255
|
},
|
|
256
|
+
predefinedPromptDisclaimer: {
|
|
257
|
+
fontSize: "0.75rem",
|
|
258
|
+
color: "#6b7280",
|
|
259
|
+
textAlign: "center",
|
|
260
|
+
marginTop: "2rem",
|
|
261
|
+
width: "100%",
|
|
262
|
+
},
|
|
255
263
|
backdrop: {
|
|
256
264
|
position: "fixed",
|
|
257
265
|
top: 0,
|
|
@@ -407,16 +415,6 @@ const ANIMATION_STYLES = `
|
|
|
407
415
|
border-color: #d1d5db !important;
|
|
408
416
|
}
|
|
409
417
|
`;
|
|
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
418
|
const ICONS = {
|
|
421
419
|
chat: "https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/icons/message-square.svg",
|
|
422
420
|
shopprompter:
|
|
@@ -454,6 +452,23 @@ function ShopPrompter(props) {
|
|
|
454
452
|
() => ""
|
|
455
453
|
);
|
|
456
454
|
|
|
455
|
+
function t() {
|
|
456
|
+
return getTranslations(props.defaultLocale);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function requireClickOnPredefinedPromptVal() {
|
|
460
|
+
const flag = props.requireClickOnPredefinedPrompt;
|
|
461
|
+
return typeof flag === "string" ? flag !== "false" : flag ?? true;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function shouldHideInput() {
|
|
465
|
+
const requireClick = requireClickOnPredefinedPromptVal();
|
|
466
|
+
if (!requireClick) return false;
|
|
467
|
+
if (chatMessages.length > 0) return false;
|
|
468
|
+
if (prompts().length === 0) return false;
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
|
|
457
472
|
function hasToken() {
|
|
458
473
|
const token =
|
|
459
474
|
props.shopPrompterToken || apiConfigObj?.()?.shopPrompterToken;
|
|
@@ -635,7 +650,13 @@ function ShopPrompter(props) {
|
|
|
635
650
|
setIsLoading(loading);
|
|
636
651
|
},
|
|
637
652
|
onError: (err) => {
|
|
638
|
-
|
|
653
|
+
if (err.includes("429")) {
|
|
654
|
+
setError(t().guestLimitReached);
|
|
655
|
+
} else if (err.includes("401") || err.includes("403")) {
|
|
656
|
+
setError(t().sessionExpired);
|
|
657
|
+
} else {
|
|
658
|
+
setError(err);
|
|
659
|
+
}
|
|
639
660
|
},
|
|
640
661
|
onCartOperation: (ops) => {
|
|
641
662
|
if (props.onCartOperation) {
|
|
@@ -794,6 +815,10 @@ function ShopPrompter(props) {
|
|
|
794
815
|
return;
|
|
795
816
|
}
|
|
796
817
|
const response = await service?.getAgentExperience();
|
|
818
|
+
if (!response) {
|
|
819
|
+
setError(t().agentExperienceError);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
797
822
|
setAgentExperience(response);
|
|
798
823
|
})();
|
|
799
824
|
}
|
|
@@ -824,7 +849,7 @@ function ShopPrompter(props) {
|
|
|
824
849
|
.catch((err) => {
|
|
825
850
|
// Revert optimistic update
|
|
826
851
|
setChatMessages(oldMessages);
|
|
827
|
-
setError(err?.message ||
|
|
852
|
+
setError(err?.message || t().feedbackError);
|
|
828
853
|
});
|
|
829
854
|
}
|
|
830
855
|
|
|
@@ -996,9 +1021,7 @@ function ShopPrompter(props) {
|
|
|
996
1021
|
</>
|
|
997
1022
|
)}
|
|
998
1023
|
{!hasToken() && apiConfigObj?.()?.apiKey ? (
|
|
999
|
-
<div style={styles.warningBanner}>
|
|
1000
|
-
API Key is only for development purposes
|
|
1001
|
-
</div>
|
|
1024
|
+
<div style={styles.warningBanner}>{t().apiKeyWarning}</div>
|
|
1002
1025
|
) : null}
|
|
1003
1026
|
<div style={styles.subHeaderContainer}>
|
|
1004
1027
|
<button
|
|
@@ -1006,7 +1029,7 @@ function ShopPrompter(props) {
|
|
|
1006
1029
|
onClick={(e) => handleNewChat()}
|
|
1007
1030
|
style={styles.headerButton}
|
|
1008
1031
|
>
|
|
1009
|
-
{
|
|
1032
|
+
{t().newChat}
|
|
1010
1033
|
</button>
|
|
1011
1034
|
{props.chatPosition === "classic-chatbot" ||
|
|
1012
1035
|
props.chatPosition === "right-panel" ||
|
|
@@ -1046,7 +1069,7 @@ function ShopPrompter(props) {
|
|
|
1046
1069
|
fontFamily: themeObj().fontFamily || undefined,
|
|
1047
1070
|
}}
|
|
1048
1071
|
>
|
|
1049
|
-
|
|
1072
|
+
{t().configErrorTitle}
|
|
1050
1073
|
</h3>
|
|
1051
1074
|
<p
|
|
1052
1075
|
style={{
|
|
@@ -1054,8 +1077,7 @@ function ShopPrompter(props) {
|
|
|
1054
1077
|
fontFamily: themeObj().fontFamily || undefined,
|
|
1055
1078
|
}}
|
|
1056
1079
|
>
|
|
1057
|
-
|
|
1058
|
-
Please check your setup.
|
|
1080
|
+
{t().configErrorMessage}
|
|
1059
1081
|
</p>
|
|
1060
1082
|
<div
|
|
1061
1083
|
style={{
|
|
@@ -1069,7 +1091,7 @@ function ShopPrompter(props) {
|
|
|
1069
1091
|
marginBottom: "0.25rem",
|
|
1070
1092
|
}}
|
|
1071
1093
|
>
|
|
1072
|
-
|
|
1094
|
+
{t().configMissingParams}
|
|
1073
1095
|
</div>
|
|
1074
1096
|
<div
|
|
1075
1097
|
style={{
|
|
@@ -1085,14 +1107,14 @@ function ShopPrompter(props) {
|
|
|
1085
1107
|
color: "#ef4444",
|
|
1086
1108
|
}}
|
|
1087
1109
|
>
|
|
1088
|
-
|
|
1110
|
+
{t().configMissingToken}
|
|
1089
1111
|
</span>
|
|
1090
1112
|
<span
|
|
1091
1113
|
style={{
|
|
1092
1114
|
color: "#ef4444",
|
|
1093
1115
|
}}
|
|
1094
1116
|
>
|
|
1095
|
-
|
|
1117
|
+
{t().configMissingApiKey}
|
|
1096
1118
|
</span>
|
|
1097
1119
|
</div>
|
|
1098
1120
|
</div>
|
|
@@ -1126,8 +1148,8 @@ function ShopPrompter(props) {
|
|
|
1126
1148
|
: ICONS.shopprompter
|
|
1127
1149
|
}
|
|
1128
1150
|
style={{
|
|
1129
|
-
width: "
|
|
1130
|
-
height: "
|
|
1151
|
+
width: "96px",
|
|
1152
|
+
height: "96px",
|
|
1131
1153
|
marginBottom: "24px",
|
|
1132
1154
|
borderRadius: "12px",
|
|
1133
1155
|
objectFit: "cover",
|
|
@@ -1140,10 +1162,12 @@ function ShopPrompter(props) {
|
|
|
1140
1162
|
themeObj().fontFamily ||
|
|
1141
1163
|
styles.welcomeTitle.fontFamily,
|
|
1142
1164
|
}}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1165
|
+
dangerouslySetInnerHTML={{
|
|
1166
|
+
__html:
|
|
1167
|
+
agentExperience?.greeting?.title ||
|
|
1168
|
+
t().welcomeToShopPrompter,
|
|
1169
|
+
}}
|
|
1170
|
+
/>
|
|
1147
1171
|
<p
|
|
1148
1172
|
style={{
|
|
1149
1173
|
...styles.welcomeDescription,
|
|
@@ -1151,15 +1175,21 @@ function ShopPrompter(props) {
|
|
|
1151
1175
|
themeObj().fontFamily ||
|
|
1152
1176
|
styles.welcomeDescription.fontFamily,
|
|
1153
1177
|
}}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1178
|
+
dangerouslySetInnerHTML={{
|
|
1179
|
+
__html:
|
|
1180
|
+
agentExperience?.greeting?.message ||
|
|
1181
|
+
t().welcomeDescription,
|
|
1182
|
+
}}
|
|
1183
|
+
/>
|
|
1158
1184
|
<div style={styles.buttonContainer}>
|
|
1159
1185
|
{prompts()?.map((item) => (
|
|
1160
1186
|
<button
|
|
1161
1187
|
key={item.prompt}
|
|
1162
|
-
style={
|
|
1188
|
+
style={{
|
|
1189
|
+
...styles.button,
|
|
1190
|
+
borderColor: themeObj().actionColor || "#e5e7eb",
|
|
1191
|
+
outlineColor: themeObj().actionColor || undefined,
|
|
1192
|
+
}}
|
|
1163
1193
|
onClick={(event) => {
|
|
1164
1194
|
setInput(item.prompt);
|
|
1165
1195
|
handleSend(item.prompt);
|
|
@@ -1169,6 +1199,17 @@ function ShopPrompter(props) {
|
|
|
1169
1199
|
</button>
|
|
1170
1200
|
))}
|
|
1171
1201
|
</div>
|
|
1202
|
+
{requireClickOnPredefinedPromptVal() &&
|
|
1203
|
+
prompts().length > 0 ? (
|
|
1204
|
+
<div
|
|
1205
|
+
style={{
|
|
1206
|
+
...styles.predefinedPromptDisclaimer,
|
|
1207
|
+
fontFamily: themeObj().fontFamily || undefined,
|
|
1208
|
+
}}
|
|
1209
|
+
>
|
|
1210
|
+
{t().predefinedPromptDisclaimer}
|
|
1211
|
+
</div>
|
|
1212
|
+
) : null}
|
|
1172
1213
|
</div>
|
|
1173
1214
|
) : null}
|
|
1174
1215
|
{!showWelcome() ? (
|
|
@@ -1307,7 +1348,9 @@ function ShopPrompter(props) {
|
|
|
1307
1348
|
{getShowProducts(message) ? (
|
|
1308
1349
|
<ProductCardList
|
|
1309
1350
|
skus={message.productSkus}
|
|
1310
|
-
defaultLocale={
|
|
1351
|
+
defaultLocale={
|
|
1352
|
+
props.defaultLocale || DEFAULT_LOCALE
|
|
1353
|
+
}
|
|
1311
1354
|
showPriceIfEmpty={showPriceIfEmptyVal()}
|
|
1312
1355
|
visibleFields={visibleFieldsList()}
|
|
1313
1356
|
/>
|
|
@@ -1319,50 +1362,59 @@ function ShopPrompter(props) {
|
|
|
1319
1362
|
</div>
|
|
1320
1363
|
) : null}
|
|
1321
1364
|
{error ? (
|
|
1322
|
-
<div style={styles.errorContainer}>
|
|
1365
|
+
<div style={styles.errorContainer}>{error}</div>
|
|
1323
1366
|
) : null}
|
|
1324
1367
|
</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}
|
|
1368
|
+
{!shouldHideInput() ? (
|
|
1369
|
+
<div
|
|
1370
|
+
style={{
|
|
1371
|
+
...styles.inputSection,
|
|
1372
|
+
backgroundColor:
|
|
1373
|
+
themeObj().mainBackgroundColor ||
|
|
1374
|
+
styles.inputSection.backgroundColor,
|
|
1375
|
+
...(error
|
|
1376
|
+
? {
|
|
1377
|
+
opacity: "0.5",
|
|
1378
|
+
pointerEvents: "none",
|
|
1379
|
+
}
|
|
1380
|
+
: {}),
|
|
1381
|
+
}}
|
|
1382
|
+
>
|
|
1383
|
+
<div style={styles.inputWrapper}>
|
|
1384
|
+
<input
|
|
1385
|
+
id="shop-prompter-input"
|
|
1386
|
+
type="text"
|
|
1387
|
+
style={styles.input}
|
|
1388
|
+
placeholder={
|
|
1389
|
+
agentExperience?.hintText || t().askMeAnything
|
|
1390
|
+
}
|
|
1391
|
+
value={input}
|
|
1392
|
+
onInput={(e) => onInputChange(e)}
|
|
1393
|
+
onKeyDown={(e) => onInputKeyDown(e)}
|
|
1394
|
+
disabled={!!error}
|
|
1395
|
+
/>
|
|
1396
|
+
<button
|
|
1397
|
+
onClick={(e) => handleSend()}
|
|
1358
1398
|
style={{
|
|
1359
|
-
|
|
1399
|
+
...styles.formButton,
|
|
1400
|
+
...styles.sendButton,
|
|
1401
|
+
backgroundColor:
|
|
1402
|
+
themeObj().actionColor ||
|
|
1403
|
+
styles.sendButton.backgroundColor,
|
|
1360
1404
|
}}
|
|
1361
|
-
|
|
1362
|
-
|
|
1405
|
+
>
|
|
1406
|
+
<img
|
|
1407
|
+
alt="Send"
|
|
1408
|
+
src={ICONS.sendHorizontal}
|
|
1409
|
+
style={{
|
|
1410
|
+
padding: "4px",
|
|
1411
|
+
}}
|
|
1412
|
+
/>
|
|
1413
|
+
</button>
|
|
1414
|
+
</div>
|
|
1415
|
+
<p style={styles.disclaimer}>{t().disclaimer}</p>
|
|
1363
1416
|
</div>
|
|
1364
|
-
|
|
1365
|
-
</div>
|
|
1417
|
+
) : null}
|
|
1366
1418
|
</div>
|
|
1367
1419
|
) : null}
|
|
1368
1420
|
</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: 'Klicken Sie 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
|
+
}
|