@shop-prompter/react 1.1.1 → 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.
@@ -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: "32px",
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: "20rem",
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: "0.5rem",
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
- setError(err);
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 || TRANSLATIONS.feedbackError);
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
- {TRANSLATIONS.newChat}
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
- Configuration Error
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
- ShopPrompter requires a secure token or API key to initialize.
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
- Missing Parameters:
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
- ✗ shopPrompterToken (not found)
1110
+ {t().configMissingToken}
1089
1111
  </span>
1090
1112
  <span
1091
1113
  style={{
1092
1114
  color: "#ef4444",
1093
1115
  }}
1094
1116
  >
1095
- ✗ apiConfig.apiKey (not found)
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: "64px",
1130
- height: "64px",
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
- {agentExperience?.greeting?.title ||
1145
- TRANSLATIONS.welcomeToShopPrompter}
1146
- </h3>
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
- {agentExperience?.greeting?.message ||
1156
- TRANSLATIONS.welcomeDescription}
1157
- </p>
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={styles.button}
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={props.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}>Error: {error}</div>
1365
+ <div style={styles.errorContainer}>{error}</div>
1323
1366
  ) : null}
1324
1367
  </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}
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
- padding: "4px",
1399
+ ...styles.formButton,
1400
+ ...styles.sendButton,
1401
+ backgroundColor:
1402
+ themeObj().actionColor ||
1403
+ styles.sendButton.backgroundColor,
1360
1404
  }}
1361
- />
1362
- </button>
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
- <p style={styles.disclaimer}>AI content may be inaccurate.</p>
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
+ }