wineater-bot 1.8.1 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wineater-bot",
3
- "version": "1.8.1",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.js",
package/src/index.js CHANGED
@@ -113,9 +113,9 @@ function openChatAfterDelay(delay = 10000) { // delay is in milliseconds
113
113
  document.addEventListener('DOMContentLoaded', () => {
114
114
  // Check if auto-opening is disabled by cookie
115
115
  const disableAutoOpen = getCookie('disableAutoOpen');
116
-
117
- if (!disableAutoOpen && shouldOpenChatAutomatically()) {
118
- openChatAfterDelay(10000); // 10-second delay
116
+
117
+ if (!disableAutoOpen) {
118
+ shouldOpenChatAutomatically()
119
119
  }
120
120
  });
121
121
 
package/src/js/api.js CHANGED
@@ -57,4 +57,29 @@ export class Api {
57
57
  throw new Error(`Error in getWineRecommendations: ${error.message}`);
58
58
  }
59
59
  }
60
+
61
+ async getPossibleDishesWithMeta(metaTitle, metaDescription) {
62
+ try {
63
+ const response = await fetch(`${this.baseURL}dish/possible_dishes`, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Authorization': `Bearer ${this.token}`,
67
+ 'Content-Type': 'application/json'
68
+ },
69
+ body: JSON.stringify({
70
+ title: metaTitle,
71
+ description: metaDescription
72
+ })
73
+ });
74
+ const data = await response.json();
75
+ console.log(data);
76
+ if (!response.ok) {
77
+ throw new Error(`API call failed with status ${response.status}: ${data.message || 'No additional error information'}`);
78
+ }
79
+ return data; // Return the list of dishes
80
+ } catch (error) {
81
+ console.error('Error fetching possible dishes:', error);
82
+ throw error;
83
+ }
84
+ }
60
85
  }
@@ -1,20 +1,64 @@
1
+ import { Api } from './api.js';
2
+ import { openChatWindowWithDishes, setAutoOpenTimer } from './ui.js'; // Import updated UI logic for opening chat with dishes
3
+
4
+ const api = new Api();
5
+
1
6
  const DEFAULT_URL_PATTERNS = {
2
7
  PRODUCT_PAGES: /producto\/[^\/]+\/$/, // Matches any product page like 'producto/any-product-name/'
3
8
  // CART_PAGE: /carrito\/$/ // Matches the cart page like 'carrito/'
4
9
  };
5
10
 
11
+ const DEFAULT_URL_ANTI_PATTERNS = {
12
+ FESTIVAL_PAGES: /festival/, // Matches any URL containing 'festival'
13
+ TASTING_PAGES: /tasting/ // Matches any URL containing 'tasting'
14
+ };
15
+
16
+ const urlAntiPatterns = {
17
+ FESTIVAL_PAGES: window.wineaterData?.FESTIVAL_PAGES || DEFAULT_URL_ANTI_PATTERNS.FESTIVAL_PAGES,
18
+ TASTING_PAGES: window.wineaterData?.TASTING_PAGES || DEFAULT_URL_ANTI_PATTERNS.TASTING_PAGES
19
+ };
20
+
6
21
  // Retrieve custom or default URL patterns from wineaterData
7
22
  const urlPatterns = {
8
23
  PRODUCT_PAGES: window.wineaterData?.PRODUCT_PAGES || DEFAULT_URL_PATTERNS.PRODUCT_PAGES,
9
24
  CART_PAGE: window.wineaterData?.CART_PAGE || DEFAULT_URL_PATTERNS?.CART_PAGE
10
25
  };
11
26
 
12
- export function shouldOpenChatAutomatically() {
27
+ export async function shouldOpenChatAutomatically() {
13
28
  const currentURL = window.location.href;
14
- console.log(urlPatterns);
15
-
16
- if (urlPatterns.PRODUCT_PAGES.test(currentURL) || urlPatterns.CART_PAGE.test(currentURL)) {
17
- return true;
29
+ // Check if the current URL matches any of the anti-patterns
30
+ for (const [key, regex] of Object.entries(urlAntiPatterns)) {
31
+ if (regex.test(currentURL)) {
32
+ return false; // Do not open chat if URL matches an anti-pattern
33
+ }
18
34
  }
19
- return false;
35
+
36
+ // Check if the current URL matches any of the patterns
37
+ for (const [key, regex] of Object.entries(urlPatterns)) {
38
+ if (regex.test(currentURL)) {
39
+ try {
40
+ // Get meta title and description from the document
41
+ const metaTitle = document.querySelector('meta[name="title"]')?.getAttribute('content') || document.title;
42
+ const metaDescription = document.querySelector('meta[name="description"]')?.getAttribute('content') || '';
43
+
44
+ // Fetch dishes based on meta data
45
+ const dishes = await api.getPossibleDishesWithMeta(metaTitle, metaDescription);
46
+
47
+ console.log(dishes);
48
+
49
+ const timer = setTimeout(() => {
50
+ openChatWindowWithDishes(dishes); // Открыть чат с блюдами через 15 секунд
51
+ }, 15000); // 15000 миллисекунд = 15 секунд
52
+
53
+ // Сохраняем таймер, чтобы можно было его сбросить при ручном открытии чата
54
+ setAutoOpenTimer(timer);
55
+ return true; // Chat opens with dish recommendations
56
+ } catch (error) {
57
+ console.error('Error fetching dishes:', error);
58
+ return false; // Do not open chat if fetching dishes fails
59
+ }
60
+ }
61
+ }
62
+
63
+ return false; // Default to not opening chat if no patterns match
20
64
  }
package/src/js/i18n.js CHANGED
@@ -3,7 +3,7 @@ import { Api } from './api.js';
3
3
  class Translator {
4
4
  constructor() {
5
5
  this.api = new Api();
6
- this.language = 'en'; // Default to French
6
+ this.language = 'en'; // Default to English
7
7
  this.translations = {
8
8
  fr: {
9
9
  welcomeMessage: "Sublimez votre expérience gastronomique. Il vous suffit de donner les détails de votre plat, et obtenez la recommandation de compagnon de vin idéale. Que voulez-vous manger?",
@@ -16,7 +16,10 @@ class Translator {
16
16
  region: "Région",
17
17
  type: "Type",
18
18
  reason: "Suggestion",
19
- moreInfo: "Plus d'infos"
19
+ moreInfo: "Plus d'infos",
20
+ winePairingIntro: "Pour ce vin, voici quelques suggestions de plats :",
21
+ askForDinner: "Que voulez-vous manger ce soir?",
22
+ clearChatButton: "Effacer le chat"
20
23
  },
21
24
  es: {
22
25
  welcomeMessage: "Mejore su experiencia gastronómica. Solo proporcione los detalles de su plato y obtenga la recomendación de vino ideal. ¿Qué le gustaría comer?",
@@ -29,7 +32,10 @@ class Translator {
29
32
  region: "Región",
30
33
  type: "Tipo",
31
34
  reason: "Sugerencia",
32
- moreInfo: "Más info"
35
+ moreInfo: "Más info",
36
+ winePairingIntro: "Para este vino, aquí tienes algunas sugerencias de platos:",
37
+ askForDinner: "¿Qué te gustaría cenar?",
38
+ clearChatButton: "Limpiar el chat"
33
39
  },
34
40
  en: {
35
41
  welcomeMessage: "Enhance your dining experience. Just provide your dish details and get the ideal wine pairing. What would you like to eat?",
@@ -42,7 +48,10 @@ class Translator {
42
48
  region: "Region",
43
49
  type: "Type",
44
50
  reason: "Suggestion",
45
- moreInfo: "More info"
51
+ moreInfo: "More info",
52
+ winePairingIntro: "For this wine, here are some suggested dishes:",
53
+ askForDinner: "What would you like to have for dinner?",
54
+ clearChatButton: "Clear Chat"
46
55
  },
47
56
  de: {
48
57
  welcomeMessage: "Verbessern Sie Ihr kulinarisches Erlebnis. Geben Sie einfach die Details Ihres Gerichts an und erhalten Sie die ideale Weinempfehlung. Was möchten Sie essen?",
@@ -55,7 +64,10 @@ class Translator {
55
64
  region: "Region",
56
65
  type: "Typ",
57
66
  reason: "Anregung",
58
- moreInfo: "Mehr Info"
67
+ moreInfo: "Mehr Info",
68
+ winePairingIntro: "Für diesen Wein gibt es folgende Essensvorschläge:",
69
+ askForDinner: "Was möchten Sie zum Abendessen essen?",
70
+ clearChatButton: "Chat löschen"
59
71
  }
60
72
  };
61
73
  }
@@ -65,7 +77,7 @@ class Translator {
65
77
  const config = await this.api.getStoreConfig(storeName);
66
78
  this.language = config.lang;
67
79
  } catch (e) {
68
- this.language = 'en';
80
+ this.language = 'en'; // Fallback to English
69
81
  }
70
82
  }
71
83
 
@@ -74,4 +86,4 @@ class Translator {
74
86
  }
75
87
  }
76
88
 
77
- export default new Translator();
89
+ export default new Translator();
package/src/js/styles.js CHANGED
@@ -1,4 +1,3 @@
1
- // src/styles.js
2
1
  const css = `
3
2
  @font-face {
4
3
  font-family: 'Poppins';
@@ -33,8 +32,6 @@ const css = `
33
32
  position: fixed;
34
33
  bottom: 20px;
35
34
  right: 20px;
36
- /* Other styles for the bottom-right button */
37
- /* ... */
38
35
  }
39
36
 
40
37
  .chat-icon-right-center {
@@ -42,35 +39,31 @@ const css = `
42
39
  top: 50%;
43
40
  right: -85px;
44
41
  transform: translateY(-50%) rotate(90deg);
45
- /* Additional styling for the button */
46
- /* ... */
47
42
  }
48
43
 
49
44
  #chat-icon img {
50
- width: 40px; /* Adjust icon size as needed */
45
+ width: 40px;
51
46
  height: auto;
52
47
  margin-right: 8px;
53
48
  }
54
49
 
55
50
  #chat-icon span {
56
51
  color: black; /* Text color */
57
- font-family: 'Poppins', sans-serif; /* Font family from your style guide */
58
- /* Add more text styles if needed */
52
+ font-family: 'Poppins', sans-serif;
59
53
  }
60
54
 
61
55
  /* Chat window styles */
62
56
  #chat-window {
63
57
  position: fixed;
64
- bottom: 20px; /* Set to the desired distance from the bottom */
65
- right: 20px; /* Set to the desired distance from the right */
66
- width: 350px; /* Width of the chat window */
58
+ bottom: 20px;
59
+ right: 20px;
60
+ width: 350px;
67
61
  background-color: white;
68
62
  border-radius: 8px;
69
63
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
70
- display: none; /* Initially hidden */
64
+ display: none;
71
65
  }
72
66
 
73
- /* Header of the chat window */
74
67
  #chat-header {
75
68
  background-color: #fff;
76
69
  color: white;
@@ -80,34 +73,30 @@ const css = `
80
73
  display: flex;
81
74
  justify-content: space-between;
82
75
  align-items: center;
83
- position: relative; /* Needed for absolute positioning of the close button */
84
76
  }
85
77
 
86
- /* Powered by and logo styles */
87
78
  #powered-by {
88
79
  display: flex;
89
80
  align-items: center;
90
- background-color: #fff; /* White background */
81
+ background-color: #fff;
91
82
  width: 100%;
92
- border-radius: 16px; /* Rounded corners for the container */
83
+ border-radius: 16px;
93
84
  }
94
85
 
95
86
  #powered-by a span {
96
87
  font-family: 'Poppins', sans-serif;
97
- color: #7E27ED; /* Text color - using the primary color from your style guide */
98
- margin-right: 8px; /* Space between the text and the logo */
88
+ color: #7E27ED;
89
+ margin-right: 8px;
99
90
  }
100
91
 
101
92
  #chatbot-logo {
102
- height: 20px; /* Adjust the logo size as needed */
103
- width: auto; /* Keep the logo's aspect ratio */
93
+ height: 20px;
94
+ width: auto;
104
95
  }
105
96
 
106
-
107
- /* Style for the close button */
108
97
  #close-btn {
109
- font-size: 24px; /* Size of the close button */
110
- color: #7E27ED; /* Text color - using the primary color from your style guide */
98
+ font-size: 24px;
99
+ color: #7E27ED;
111
100
  cursor: pointer;
112
101
  padding: 0 10px;
113
102
  line-height: 24px;
@@ -119,67 +108,80 @@ const css = `
119
108
  height: 400px;
120
109
  overflow-y: auto;
121
110
  padding: 15px;
122
- background: #f7f7f7; /* Light grey background for the message area */
111
+ background: #f7f7f7;
123
112
  }
124
113
 
125
114
  /* Input area */
126
115
  #input-container {
127
- border-top: 2px solid #ececec; /* Light border for input area */
128
- background-color: white; /* White background for input area */
116
+ border-top: 2px solid #ececec;
117
+ background-color: white;
129
118
  padding: 12px;
130
119
  display: flex;
131
- align-items: center; /* Align button and input vertically */
132
- gap: 10px; /* Adds space between input and button */
120
+ align-items: center;
121
+ gap: 10px;
133
122
  }
134
123
 
135
124
  #input-container input {
136
125
  flex-grow: 1;
137
- border: 2px solid #B928EB; /* Input border color based on style guide */
126
+ border: 2px solid #B928EB;
138
127
  padding: 10px;
139
128
  border-radius: 4px;
140
- font-family: 'Poppins', sans-serif; /* Use Poppins font */
129
+ font-family: 'Poppins', sans-serif;
141
130
  }
142
131
 
143
132
  #input-container button {
144
- padding: 10px 20px; /* Larger padding for a larger button */
145
- background-color: #7E27ED; /* Button background color from style guide */
133
+ padding: 10px 20px;
134
+ background-color: #7E27ED;
146
135
  border: none;
147
136
  color: white;
148
137
  border-radius: 4px;
149
- font-family: 'Poppins', sans-serif; /* Use Poppins font */
150
- font-size: 1rem; /* Adjust font size as needed */
138
+ font-family: 'Poppins', sans-serif;
139
+ font-size: 1rem;
151
140
  cursor: pointer;
152
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Add subtle shadow for depth */
153
- transition: background-color 0.3s ease; /* Smooth transition for background color */
141
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
142
+ transition: background-color 0.3s ease;
154
143
  }
155
144
 
156
145
  #input-container button:hover {
157
- background-color: #2FC0BF; /* Hover color based on style guide */
158
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Darker shadow for hover state */
146
+ background-color: #2FC0BF;
147
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
159
148
  }
160
149
 
161
150
  /* Message bubble styling */
162
151
  .message {
163
152
  margin: 8px 0;
164
153
  padding: 12px;
165
- background-color: #e0e0e0; /* Light grey for message bubbles */
154
+ background-color: #e0e0e0;
166
155
  border-radius: 16px;
167
156
  font-size: 14px;
168
157
  line-height: 1.4;
169
158
  }
170
159
 
171
160
  .message.user {
172
- background-color: #B928EB; /* User message bubble color */
161
+ background-color: #B928EB;
173
162
  color: white;
174
163
  align-self: flex-end;
175
164
  }
176
165
 
177
166
  .message.api {
178
- background-color: #e0e0e0; /* API message bubble color */
167
+ background-color: #e0e0e0;
179
168
  color: black;
180
169
  align-self: flex-start;
181
170
  }
182
171
 
172
+ /* Dish item styling */
173
+ .dish-message {
174
+ border: 2px solid #7E27ED; /* Bold yellow border */
175
+ cursor: pointer;
176
+ padding: 14px;
177
+ font-weight: bold;
178
+ font-size: 16px;
179
+ border-radius: 12px;
180
+ color: #333;
181
+ margin-top: 10px;
182
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Light shadow for depth */
183
+ }
184
+
183
185
  /* Wine item styling */
184
186
  .wine-item {
185
187
  display: flex;
@@ -188,7 +190,7 @@ const css = `
188
190
  border-radius: 8px;
189
191
  padding: 10px;
190
192
  margin-top: 10px;
191
- background: white; /* White background for wine items */
193
+ background: white;
192
194
  }
193
195
 
194
196
  .wine-item a {
@@ -196,14 +198,14 @@ const css = `
196
198
  }
197
199
 
198
200
  .wine-item img {
199
- width: 80px; /* Smaller wine image size */
201
+ width: 80px;
200
202
  height: auto;
201
- border-radius: 4px; /* Rounded corners for images */
203
+ border-radius: 4px;
202
204
  margin-right: 12px;
203
205
  }
204
206
 
205
207
  .wine-item div {
206
- font-size: 0.9rem; /* Smaller font size for wine info */
208
+ font-size: 0.9rem;
207
209
  }
208
210
 
209
211
  /* Ensure the chat window is visible when the 'visible' class is added */
@@ -213,14 +215,14 @@ const css = `
213
215
 
214
216
  /* Loader Styles */
215
217
  .loader {
216
- display: none; /* Loader is hidden by default */
217
- border: 4px solid #f3f3f3; /* Light grey */
218
- border-top: 4px solid #3498db; /* Blue, could be your primary color */
218
+ display: none;
219
+ border: 4px solid #f3f3f3;
220
+ border-top: 4px solid #3498db;
219
221
  border-radius: 50%;
220
222
  width: 40px;
221
223
  height: 40px;
222
224
  animation: spin 2s linear infinite;
223
- margin: 20px auto; /* Centered horizontally */
225
+ margin: 20px auto;
224
226
  }
225
227
 
226
228
  .loader.visible {
@@ -237,8 +239,8 @@ const css = `
237
239
  display: flex;
238
240
  justify-content: center;
239
241
  align-items: center;
240
- height: 0; /* Adjust as needed */
241
- visibility: hidden; /* Start off invisible */
242
+ height: 0;
243
+ visibility: hidden;
242
244
  background-color: #f7f7f7;
243
245
  }
244
246
 
@@ -247,13 +249,12 @@ const css = `
247
249
  width: 8px;
248
250
  height: 8px;
249
251
  margin: 0 4px;
250
- background-color: #3498db; /* Or use a color from your style guide */
252
+ background-color: #3498db;
251
253
  border-radius: 50%;
252
254
  opacity: 0.4;
253
255
  animation: typing 1.5s infinite;
254
256
  }
255
257
 
256
- /* Animation keyframes */
257
258
  @keyframes typing {
258
259
  0% { transform: translateY(0); }
259
260
  20% { transform: translateY(-10px); }
@@ -263,7 +264,7 @@ const css = `
263
264
  100% { transform: translateY(0); }
264
265
  }
265
266
 
266
- /* To stagger the animation for each dot */
267
+ /* Staggered animation for typing dots */
267
268
  .typing-indicator span:nth-child(1) {
268
269
  animation-delay: 0s;
269
270
  }
package/src/js/ui.js CHANGED
@@ -1,7 +1,9 @@
1
1
 
2
2
  import { trackChatbotOpen, trackChatbotClose, trackChatbotOpenAuto, setCookie, getCookie, deleteCookie } from './analytics.js';
3
+ import translator from './i18n.js';
3
4
 
4
5
  const MAX_CHAT_CLOSURES = 3;
6
+ let timerId = null; // Переменная для хранения ID таймера
5
7
 
6
8
  export function toggleChatWindow() {
7
9
  const chatWindow = document.getElementById('chat-window');
@@ -16,6 +18,10 @@ export function toggleChatWindow() {
16
18
  closeChatWindow();
17
19
  chatIcon.style.display = 'flex'; // Show the chat icon/button
18
20
  } else {
21
+ if (timerId) {
22
+ clearTimeout(timerId);
23
+ timerId = null; // Сбрасываем таймер
24
+ }
19
25
  trackChatbotOpen();
20
26
  chatWindow.classList.add('visible');
21
27
  chatIcon.style.display = 'none'; // Hide the chat icon/button
@@ -41,6 +47,45 @@ export function closeChatWindow() {
41
47
  }
42
48
  }
43
49
 
50
+ export function openChatWindowWithDishes(dishes) {
51
+ const chatWindow = document.getElementById('chat-window');
52
+ const messageContainer = document.getElementById('message-container');
53
+ const inputField = document.querySelector('#input-container input'); // Инпут для текста пользователя
54
+
55
+ // Очистить существующие сообщения
56
+ messageContainer.innerHTML = '';
57
+
58
+ chatWindow.classList.add('visible'); // Открыть окно чата
59
+
60
+ // Добавить сообщение о предложенных блюдах
61
+ const introMessage = document.createElement('div');
62
+ introMessage.className = 'message api';
63
+ introMessage.textContent = translator.translate('winePairingIntro');
64
+ messageContainer.appendChild(introMessage);
65
+
66
+ // Показать блюда с кликабельностью
67
+ dishes.slice(0, 3).forEach(dish => {
68
+ const dishMessage = document.createElement('div');
69
+ dishMessage.className = 'message dish-message'; // Используем новый стиль для блюда
70
+ dishMessage.textContent = dish; // Предполагаем, что в ответе от API есть поле "name"
71
+
72
+ // Добавляем событие клика для подстановки названия в инпут
73
+ dishMessage.addEventListener('click', () => {
74
+ inputField.value = dish; // Подставляем название блюда в инпут
75
+ });
76
+
77
+ messageContainer.appendChild(dishMessage);
78
+ });
79
+
80
+ // Добавляем вопрос пользователю
81
+ const followUpMessage = document.createElement('div');
82
+ followUpMessage.className = 'message api';
83
+ followUpMessage.textContent = translator.translate('askForDinner');
84
+ messageContainer.appendChild(followUpMessage);
85
+
86
+ messageContainer.scrollTop = messageContainer.scrollHeight; // Скролл до последнего сообщения
87
+ }
88
+
44
89
  export function openChatWindow(fromUrl = null) {
45
90
  if (!!fromUrl) {
46
91
  trackChatbotOpenAuto(fromUrl);
@@ -55,4 +100,8 @@ export function openChatWindow(fromUrl = null) {
55
100
  document.addEventListener('DOMContentLoaded', () => {
56
101
  setupCloseButton();
57
102
  setupOutsideClick();
58
- });
103
+ });
104
+
105
+ export function setAutoOpenTimer(timer) {
106
+ timerId = timer;
107
+ }