@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.
@@ -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: "1px solid #e5e7eb",
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: "32px",
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: "20rem",
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: "0.5rem",
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
- setError(err);
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 || TRANSLATIONS.feedbackError);
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
- {TRANSLATIONS.newChat}
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
- Configuration Error
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
- ShopPrompter requires a secure token or API key to initialize.
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
- Missing Parameters:
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
- ✗ shopPrompterToken (not found)
1111
+ {t().configMissingToken}
1089
1112
  </span>
1090
1113
  <span
1091
1114
  style={{
1092
1115
  color: "#ef4444",
1093
1116
  }}
1094
1117
  >
1095
- ✗ apiConfig.apiKey (not found)
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: "64px",
1130
- height: "64px",
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
- {agentExperience?.greeting?.title ||
1145
- TRANSLATIONS.welcomeToShopPrompter}
1146
- </h3>
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
- {agentExperience?.greeting?.message ||
1156
- TRANSLATIONS.welcomeDescription}
1157
- </p>
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={styles.button}
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={props.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}>Error: {error}</div>
1366
+ <div style={styles.errorContainer}>{error}</div>
1323
1367
  ) : null}
1324
1368
  </div>
1325
- <div
1326
- style={{
1327
- ...styles.inputSection,
1328
- backgroundColor:
1329
- themeObj().mainBackgroundColor ||
1330
- styles.inputSection.backgroundColor,
1331
- }}
1332
- >
1333
- <div style={styles.inputWrapper}>
1334
- <input
1335
- id="shop-prompter-input"
1336
- type="text"
1337
- style={styles.input}
1338
- placeholder={
1339
- agentExperience?.hintText || TRANSLATIONS.askMeAnything
1340
- }
1341
- value={input}
1342
- onInput={(e) => onInputChange(e)}
1343
- onKeyDown={(e) => onInputKeyDown(e)}
1344
- />
1345
- <button
1346
- onClick={(e) => handleSend()}
1347
- style={{
1348
- ...styles.formButton,
1349
- ...styles.sendButton,
1350
- backgroundColor:
1351
- themeObj().actionColor ||
1352
- styles.sendButton.backgroundColor,
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
- padding: "4px",
1400
+ ...styles.formButton,
1401
+ ...styles.sendButton,
1402
+ backgroundColor:
1403
+ themeObj().actionColor ||
1404
+ styles.sendButton.backgroundColor,
1360
1405
  }}
1361
- />
1362
- </button>
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
- <p style={styles.disclaimer}>AI content may be inaccurate.</p>
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
+ }