wineater-bot 3.2.1 → 3.4.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/dist/wineater-chatbot.mjs +2265 -3036
- package/dist/wineater-chatbot.umd.js +252 -773
- package/index.html +1 -1
- package/package.json +1 -1
- package/src/index.js +1 -95
- package/src/js/i18n.js +29 -0
- package/src/js/widget-styles.js +183 -2
- package/src/js/widget.js +130 -271
- package/src/css/styles.css +0 -239
- package/src/js/auto-open.js +0 -64
- package/src/js/barConfig.js +0 -54
- package/src/js/chatbot.js +0 -140
- package/src/js/styles.js +0 -651
- package/src/js/ui.js +0 -107
package/src/js/widget.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import './widget-styles.js';
|
|
3
3
|
import { Api } from './api.js';
|
|
4
4
|
import translator from './i18n.js';
|
|
5
|
-
import { barConfig } from './barConfig.js';
|
|
6
5
|
|
|
7
6
|
export class WineWidget {
|
|
8
7
|
constructor() {
|
|
@@ -21,24 +20,12 @@ export class WineWidget {
|
|
|
21
20
|
intensity: null,
|
|
22
21
|
};
|
|
23
22
|
|
|
24
|
-
// Если bar — берем название и список блюд из barConfig
|
|
25
|
-
if (this.widgetType === 'bar') {
|
|
26
|
-
this.barName = barConfig.barName; // "LENEZ"
|
|
27
|
-
this.barDishes = barConfig.dishes; // Список блюд из barConfig
|
|
28
|
-
} else {
|
|
29
|
-
this.barName = '';
|
|
30
|
-
this.barDishes = [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
23
|
// Инициализируем виджет
|
|
34
24
|
this.initializeWidget();
|
|
35
25
|
this.initializeSearchAnimation();
|
|
36
26
|
this.sendEvent('widgetInitialized', { widgetType: this.widgetType });
|
|
27
|
+
this.loadInstantRecommendations();
|
|
37
28
|
|
|
38
|
-
// Если нужна мгновенная загрузка рекомендаций только в store
|
|
39
|
-
if (this.widgetType === 'store') {
|
|
40
|
-
this.loadInstantRecommendations();
|
|
41
|
-
}
|
|
42
29
|
}
|
|
43
30
|
|
|
44
31
|
sendEvent(eventType, data) {
|
|
@@ -65,7 +52,6 @@ export class WineWidget {
|
|
|
65
52
|
|
|
66
53
|
initializeWidget() {
|
|
67
54
|
const widgetContainer = document.getElementById(this.widgetWrapper);
|
|
68
|
-
if (this.widgetType === 'store') {
|
|
69
55
|
const widgetHTML = `
|
|
70
56
|
<div id="wine-widget-container">
|
|
71
57
|
<div class="wine-widget-wrapper">
|
|
@@ -108,57 +94,6 @@ export class WineWidget {
|
|
|
108
94
|
`;
|
|
109
95
|
widgetContainer.insertAdjacentHTML('beforeend', widgetHTML);
|
|
110
96
|
this.addEventListenersStoreVersion();
|
|
111
|
-
} else {
|
|
112
|
-
const barHTML = `
|
|
113
|
-
<div id="wine-widget-container" class="bar-widget">
|
|
114
|
-
<div id="wine-widget-right">
|
|
115
|
-
${barConfig.dishGroups.map(group => `
|
|
116
|
-
<!-- Обёртка группы -->
|
|
117
|
-
<div class="dish-group">
|
|
118
|
-
<!-- Заголовок группы, если нужен -->
|
|
119
|
-
<!-- Сетка блюд конкретной группы -->
|
|
120
|
-
<div class="dish-grid">
|
|
121
|
-
${group.dishes.map(dish => `
|
|
122
|
-
<div class="dish-tile" data-dish="${dish}">
|
|
123
|
-
${dish}
|
|
124
|
-
</div>
|
|
125
|
-
`).join('')}
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
`).join('')}
|
|
129
|
-
|
|
130
|
-
<div id="wine-placeholder"></div>
|
|
131
|
-
|
|
132
|
-
<div id="loading-indicator" class="hidden">
|
|
133
|
-
<div id="typing-indicator" class="typing-indicator">
|
|
134
|
-
<span></span><span></span><span></span>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<!-- Кнопки для отправки и сброса -->
|
|
139
|
-
<div id="bar-controls">
|
|
140
|
-
<input
|
|
141
|
-
type="text"
|
|
142
|
-
id="bar-notes-input"
|
|
143
|
-
placeholder="${translator.translate('barNotesPlaceholder')}"
|
|
144
|
-
/>
|
|
145
|
-
<button id="bar-send-button">
|
|
146
|
-
${translator.translate('barSendButton')}
|
|
147
|
-
</button>
|
|
148
|
-
<button id="bar-reset-button" class="hidden">
|
|
149
|
-
${translator.translate('barResetButton')}
|
|
150
|
-
</button>
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
`;
|
|
156
|
-
widgetContainer.insertAdjacentHTML('beforeend', barHTML);
|
|
157
|
-
|
|
158
|
-
// Привязка обработчиков "плиток" и кнопки
|
|
159
|
-
this.addEventListenersBarVersion();
|
|
160
|
-
}
|
|
161
|
-
this.initializeSearchAnimation();
|
|
162
97
|
}
|
|
163
98
|
|
|
164
99
|
addEventListenersStoreVersion() {
|
|
@@ -267,122 +202,142 @@ export class WineWidget {
|
|
|
267
202
|
}
|
|
268
203
|
|
|
269
204
|
displayResults(wines, isAnimated = false) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
205
|
+
const resultsContainer = document.getElementById('wine-placeholder');
|
|
206
|
+
this.hideLoadingIndicator();
|
|
207
|
+
resultsContainer.innerHTML = '';
|
|
208
|
+
|
|
209
|
+
this.sendEvent('resultsDisplayed', { resultsCount: wines.length });
|
|
210
|
+
|
|
211
|
+
const getValue = (wine, key) => wine[key] || wine[key.charAt(0).toUpperCase() + key.slice(1)];
|
|
212
|
+
const joinParams = (wine, keys) =>
|
|
213
|
+
keys.map(key => getValue(wine, key)).filter(Boolean).join(' • ');
|
|
214
|
+
|
|
215
|
+
const createTagHTML = tags =>
|
|
216
|
+
Array.isArray(tags)
|
|
217
|
+
? tags.map(tag => `<span class="popup-tag">${tag}</span>`).join('')
|
|
218
|
+
: '';
|
|
219
|
+
|
|
220
|
+
const createPopupHTML = (wine) => {
|
|
221
|
+
const tags = wine.tags || wine.Tags;
|
|
222
|
+
const text = wine.reason || wine['Short Description'];
|
|
223
|
+
if (!text && !tags) return '';
|
|
224
|
+
return `
|
|
225
|
+
<div class="wine-hover-popup">
|
|
226
|
+
${text ? `<div class="popup-reason">${text}</div>` : ''}
|
|
227
|
+
${tags ? `<div class="popup-tags">${createTagHTML(tags)}</div>` : ''}
|
|
228
|
+
</div>`;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
wines.forEach((wine, index) => {
|
|
232
|
+
const title = getValue(wine, 'title');
|
|
233
|
+
const image = getValue(wine, 'image');
|
|
234
|
+
const price = getValue(wine, 'total');
|
|
235
|
+
const url = getValue(wine, 'url');
|
|
236
|
+
const tags = wine.tags || wine.Tags;
|
|
237
|
+
const line1 = joinParams(wine, ['type', 'country', 'region', 'grape']);
|
|
238
|
+
const tagsHTML = tags ? createTagHTML(tags) : '';
|
|
239
|
+
const reasonPopupHTML = createPopupHTML(wine);
|
|
240
|
+
const shortDescription = wine['Short Description'];
|
|
241
|
+
const pairing = wine.pairing_recommendation;
|
|
242
|
+
const isSomm = wine.isSommSelection;
|
|
243
|
+
|
|
244
|
+
const wineHTML = `
|
|
245
|
+
<div class="wine-recommendation-tile ${isSomm ? 'somm-selection' : ''}">
|
|
246
|
+
${isSomm ? `<div class="somm-selection-badge">${translator.translate('wineDetails.sommSelection')}</div>` : ''}
|
|
247
|
+
|
|
248
|
+
<div class="tile-top">
|
|
249
|
+
${image ? `<img src="${image}" alt="${title}" class="wine-image" />` : ''}
|
|
250
|
+
<div class="wine-price">${price} ${translator.getCurrency('label')}</div>
|
|
251
|
+
<h3 class="wine-title">${title}</h3>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
${line1 ? `<div class="wine-params">${line1}</div>` : ''}
|
|
255
|
+
|
|
256
|
+
<div class="bottom-row">
|
|
257
|
+
${url
|
|
258
|
+
? `<a class="buy-btn" href="${url}" target="_blank">${translator.translate('wineDetails.buy')}</a>`
|
|
259
|
+
: `<button class="buy-btn" data-toggle-info>${translator.translate('moreInfo')}</button>`
|
|
260
|
+
}
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div class="card-popup hidden">
|
|
264
|
+
<div class="card-popup-header">
|
|
265
|
+
<h2>${title}</h2>
|
|
266
|
+
<span class="card-popup-close">×</span>
|
|
267
|
+
</div>
|
|
268
|
+
${shortDescription ? `<p>${shortDescription}</p>` : ''}
|
|
269
|
+
${wine.reason ? `<div class="popup-reason">${wine.reason}</div>` : pairing ? `<p>${pairing}</p>` : ''}
|
|
270
|
+
${tagsHTML ? `<div class="popup-tags">${tagsHTML}</div>` : ''}
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
${reasonPopupHTML}
|
|
274
|
+
</div>
|
|
275
|
+
`;
|
|
276
|
+
|
|
277
|
+
resultsContainer.insertAdjacentHTML('beforeend', wineHTML);
|
|
278
|
+
const currentCard = resultsContainer.children[index];
|
|
279
|
+
|
|
280
|
+
if (!url) {
|
|
281
|
+
const buyBtn = currentCard.querySelector('[data-toggle-info]');
|
|
282
|
+
const popup = currentCard.querySelector('.card-popup');
|
|
283
|
+
const closeBtn = currentCard.querySelector('.card-popup-close');
|
|
284
|
+
|
|
285
|
+
closeBtn?.addEventListener('click', (e) => {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
popup.classList.add('hidden');
|
|
288
|
+
});
|
|
278
289
|
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
290
|
+
document.addEventListener('click', (e) => {
|
|
291
|
+
const isInsideAnyPopup =
|
|
292
|
+
e.target.closest('.card-popup') ||
|
|
293
|
+
e.target.closest('.mobile-fullscreen-popup') ||
|
|
294
|
+
e.target.closest('[data-toggle-info]');
|
|
295
|
+
|
|
296
|
+
if (!isInsideAnyPopup) {
|
|
297
|
+
document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
|
|
298
|
+
document.querySelectorAll('.mobile-fullscreen-popup').forEach(p => {
|
|
299
|
+
const wrapper = p.closest('.wine-widget-context');
|
|
300
|
+
if (wrapper) wrapper.remove();
|
|
301
|
+
else p.remove();
|
|
302
|
+
});
|
|
283
303
|
}
|
|
284
|
-
|
|
285
|
-
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
buyBtn?.addEventListener('click', (e) => {
|
|
307
|
+
e.stopPropagation();
|
|
308
|
+
|
|
309
|
+
// Закрыть все открытые popup'ы
|
|
310
|
+
document.querySelectorAll('.card-popup').forEach(p => p.classList.add('hidden'));
|
|
311
|
+
document.querySelectorAll('.mobile-fullscreen-wrapper').forEach(p => p.remove());
|
|
312
|
+
|
|
313
|
+
if (window.innerWidth <= 768) {
|
|
314
|
+
const popupContent = popup.innerHTML;
|
|
315
|
+
|
|
316
|
+
const overlay = document.createElement('div');
|
|
317
|
+
overlay.className = 'mobile-fullscreen-wrapper';
|
|
318
|
+
overlay.innerHTML = `
|
|
319
|
+
<div class="mobile-fullscreen-overlay"></div>
|
|
320
|
+
<div class="card-popup mobile-fullscreen-popup">${popupContent}</div>
|
|
321
|
+
`;
|
|
322
|
+
|
|
323
|
+
overlay.querySelector('.card-popup-close')?.addEventListener('click', () => overlay.remove());
|
|
324
|
+
|
|
325
|
+
document.body.appendChild(overlay);
|
|
326
|
+
} else {
|
|
327
|
+
popup.classList.remove('hidden');
|
|
286
328
|
}
|
|
287
|
-
|
|
288
|
-
paramChips.push(wine.region || wine.Region);
|
|
289
|
-
};
|
|
290
|
-
if (wine.grape || wine.Grape) {
|
|
291
|
-
paramChips.push(wine.grape || wine.Grape);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const paramList = [];
|
|
295
|
-
if (wine.type || wine.Type) paramList.push(wine.type || wine.Type);
|
|
296
|
-
if (wine.country || wine.Country) paramList.push(wine.country || wine.Country);
|
|
297
|
-
// if (wine.region || wine.Region) paramList.push(wine.region || wine.Region);
|
|
298
|
-
// if (wine.grape || wine.Grape) paramList.push(wine.grape || wine.Grape);
|
|
299
|
-
|
|
300
|
-
// Допустим, первые 3 идут в одну строку, а если есть 4‑й,
|
|
301
|
-
// мы выведем его на вторую строку. Или всё в одну строку — на вкус.
|
|
302
|
-
const line1 = paramList.join(' • ');
|
|
303
|
-
// const line2 = paramList.slice(3).join(' • ');
|
|
304
|
-
const tags = wine.tags || wine.Tags;
|
|
305
|
-
const reasonPopupHTML = (wine.reason || tags)
|
|
306
|
-
? `
|
|
307
|
-
<div class="wine-hover-popup">
|
|
308
|
-
${wine.reason ? `<div class="popup-reason">${wine.reason}</div>` : ''}
|
|
309
|
-
${
|
|
310
|
-
tags
|
|
311
|
-
? `<div class="popup-tags">${
|
|
312
|
-
Array.isArray(tags)
|
|
313
|
-
// Для каждого тега делаем отдельный span
|
|
314
|
-
? tags.map(tag => `<span class="popup-tag">${tag}</span>`).join('')
|
|
315
|
-
: tags
|
|
316
|
-
}
|
|
317
|
-
</div>`
|
|
318
|
-
: ''
|
|
319
|
-
}
|
|
320
|
-
</div>`
|
|
321
|
-
: '';
|
|
322
|
-
|
|
323
|
-
const wineHTML = `
|
|
324
|
-
<div class="wine-recommendation-tile ${wine.isSommSelection ? 'somm-selection' : ''}">
|
|
325
|
-
${
|
|
326
|
-
wine.isSommSelection
|
|
327
|
-
? `<div class="somm-selection-badge">
|
|
328
|
-
${translator.translate('wineDetails.sommSelection')}
|
|
329
|
-
</div>`
|
|
330
|
-
: ''
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
<div class="tile-top">
|
|
334
|
-
<!-- Большое изображение по центру -->
|
|
335
|
-
${
|
|
336
|
-
(wine.image || wine.Image)
|
|
337
|
-
? `<img src="${wine.image || wine.Image}"
|
|
338
|
-
alt="${wine.title || wine.Title}"
|
|
339
|
-
class="wine-image" />`
|
|
340
|
-
: ''
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
<!-- Цена над названием -->
|
|
344
|
-
<div class="wine-price">
|
|
345
|
-
${wine.price || wine.Price}
|
|
346
|
-
</div>
|
|
347
|
-
|
|
348
|
-
<!-- Название вина (больше шрифт) -->
|
|
349
|
-
<h2 class="wine-title">
|
|
350
|
-
${wine.title || wine.Title}
|
|
351
|
-
</h2>
|
|
352
|
-
</div>
|
|
353
|
-
|
|
354
|
-
<!-- Параметры через точки -->
|
|
355
|
-
${
|
|
356
|
-
line1
|
|
357
|
-
? `<div class="wine-params">${line1}</div>`
|
|
358
|
-
: ''
|
|
329
|
+
});
|
|
359
330
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
<!-- Кнопка Buy, видна только при наведении -->
|
|
363
|
-
<div class="bottom-row">
|
|
364
|
-
<a class="buy-btn" href="${wine.url || wine.Url}" target="_blank">
|
|
365
|
-
${translator.translate('wineDetails.buy')}
|
|
366
|
-
</a>
|
|
367
|
-
</div>
|
|
368
|
-
|
|
369
|
-
<!-- Добавляем HTML попапа -->
|
|
370
|
-
${reasonPopupHTML}
|
|
371
|
-
</div>
|
|
372
|
-
`;
|
|
373
|
-
resultsContainer.insertAdjacentHTML('beforeend', wineHTML);
|
|
374
331
|
|
|
375
|
-
|
|
376
|
-
|
|
332
|
+
setTimeout(() => {
|
|
333
|
+
currentCard.style.opacity = '1';
|
|
334
|
+
currentCard.style.transform = 'translateY(0)';
|
|
335
|
+
}, isAnimated ? index * 200 : 0);
|
|
336
|
+
});
|
|
377
337
|
|
|
378
|
-
|
|
379
|
-
setTimeout(() => {
|
|
380
|
-
currentCard.style.opacity = '1';
|
|
381
|
-
currentCard.style.transform = 'translateY(0)';
|
|
382
|
-
}, isAnimated ? index * 200 : 0); // Задержка между карточками
|
|
383
|
-
});
|
|
384
|
-
|
|
338
|
+
if (this.widgetType === 'store') {
|
|
385
339
|
this.enableHoverPopups();
|
|
340
|
+
}
|
|
386
341
|
}
|
|
387
342
|
|
|
388
343
|
animateCardDisappearance() {
|
|
@@ -437,102 +392,6 @@ export class WineWidget {
|
|
|
437
392
|
console.error('Error loading instant recommendations:', error.message);
|
|
438
393
|
}
|
|
439
394
|
}
|
|
440
|
-
addEventListenersBarVersion() {
|
|
441
|
-
// Теперь плитки блюд появятся только после renderDishGroups()
|
|
442
|
-
const tileElements = document.querySelectorAll('.dish-tile');
|
|
443
|
-
tileElements.forEach(tile => {
|
|
444
|
-
tile.addEventListener('click', () => {
|
|
445
|
-
tile.classList.toggle('selected');
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
// Собираем все группы (или все .dish-grid)
|
|
450
|
-
const dishGroups = document.querySelectorAll('.dish-group');
|
|
451
|
-
|
|
452
|
-
// Находим элементы управления
|
|
453
|
-
const sendButton = document.getElementById('bar-send-button');
|
|
454
|
-
const resetButton = document.getElementById('bar-reset-button');
|
|
455
|
-
const notesInput = document.getElementById('bar-notes-input');
|
|
456
|
-
const winePlaceholder = document.getElementById('wine-placeholder');
|
|
457
|
-
|
|
458
|
-
if (!sendButton || !resetButton || !notesInput || !winePlaceholder) return;
|
|
459
|
-
|
|
460
|
-
// Нажатие "Отправить"
|
|
461
|
-
sendButton.addEventListener('click', async () => {
|
|
462
|
-
dishGroups.forEach(g => g.classList.add('hidden'));
|
|
463
|
-
this.showLoadingIndicator();
|
|
464
|
-
|
|
465
|
-
// 1) Собираем выбранные блюда
|
|
466
|
-
const selectedDishes = [];
|
|
467
|
-
document.querySelectorAll('.dish-tile.selected').forEach(tile => {
|
|
468
|
-
selectedDishes.push(tile.dataset.dish);
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
// 2) Берём пожелания (notes)
|
|
472
|
-
const notes = notesInput.value.trim(); // не стираем и не скрываем поле
|
|
473
|
-
|
|
474
|
-
// 3) Формируем запрос
|
|
475
|
-
const queryParts = [];
|
|
476
|
-
if (selectedDishes.length > 0) {
|
|
477
|
-
queryParts.push(`Dishes: ${selectedDishes.join(', ')}`);
|
|
478
|
-
}
|
|
479
|
-
if (notes) {
|
|
480
|
-
queryParts.push(`Notes: ${notes}`);
|
|
481
|
-
}
|
|
482
|
-
const finalQuery = queryParts.join('; ');
|
|
483
|
-
|
|
484
|
-
console.log('Final query =>', finalQuery);
|
|
485
|
-
|
|
486
|
-
this.sendEvent('barSearchPerformed', { query: finalQuery, selectedDishes, notes });
|
|
487
|
-
|
|
488
|
-
try {
|
|
489
|
-
// 4) Делаете запрос к API
|
|
490
|
-
const results = await this.api.getWineRecommendationsSearch(finalQuery);
|
|
491
|
-
|
|
492
|
-
// 5) Скрываем группы блюд (если хотите, чтобы больше не мешались)
|
|
493
|
-
|
|
494
|
-
// 6) Отображаем результаты
|
|
495
|
-
if (results && results.length > 0) {
|
|
496
|
-
this.displayResults(results, true);
|
|
497
|
-
} else {
|
|
498
|
-
this.displayNoResultsMessage();
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// 7) Показываем кнопку «Сбросить»
|
|
502
|
-
resetButton.classList.remove('hidden');
|
|
503
|
-
|
|
504
|
-
// 8) Кнопку «Отправить» НЕ скрываем — оставляем!
|
|
505
|
-
// ...никакого sendButton.classList.add('hidden')
|
|
506
|
-
} catch (error) {
|
|
507
|
-
console.error('Error while fetching bar recommendations:', error);
|
|
508
|
-
this.displayErrorMessage();
|
|
509
|
-
} finally {
|
|
510
|
-
this.hideLoadingIndicator();
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
// Нажатие "Сбросить"
|
|
515
|
-
resetButton.addEventListener('click', () => {
|
|
516
|
-
// 1) Показываем все группы (блюда)
|
|
517
|
-
dishGroups.forEach(g => g.classList.remove('hidden'));
|
|
518
|
-
|
|
519
|
-
// 2) Снимаем выделение с плиток
|
|
520
|
-
tileElements.forEach(tile => tile.classList.remove('selected'));
|
|
521
|
-
|
|
522
|
-
// 3) Очищаем результаты
|
|
523
|
-
winePlaceholder.innerHTML = '';
|
|
524
|
-
|
|
525
|
-
// 4) Прячем кнопку «Сбросить»
|
|
526
|
-
resetButton.classList.add('hidden');
|
|
527
|
-
|
|
528
|
-
notesInput.value = '';
|
|
529
|
-
|
|
530
|
-
// 5) Инпут с пожеланиями НЕ трогаем — пусть остается, пользователь может менять
|
|
531
|
-
// notesInput.value = ''; // Если захотите — можете раскомментить, чтобы тоже сбрасывать
|
|
532
|
-
|
|
533
|
-
// 6) «Отправить» и дальше на месте, не меняется
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
395
|
|
|
537
396
|
initializeSearchAnimation() {
|
|
538
397
|
const searchInput = document.getElementById('wine-search-input');
|
package/src/css/styles.css
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
@font-face {
|
|
2
|
-
font-family: 'Poppins';
|
|
3
|
-
src: url('https://fonts.googleapis.com/css2?family=Poppins:wght@400&display=swap');
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/* Overall chatbot styles */
|
|
7
|
-
#chatbot-container {
|
|
8
|
-
position: fixed;
|
|
9
|
-
bottom: 20px;
|
|
10
|
-
right: 20px;
|
|
11
|
-
font-family: 'Poppins', sans-serif; /* Use Poppins as the main font */
|
|
12
|
-
z-index: 1000; /* Ensure chat is above other content */
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* Styling the chat icon */
|
|
16
|
-
#chat-icon {
|
|
17
|
-
display: flex; /* Use flexbox for alignment */
|
|
18
|
-
align-items: center; /* Center items vertically */
|
|
19
|
-
justify-content: center; /* Center items horizontally */
|
|
20
|
-
padding: 8px; /* Add some padding around the text and image */
|
|
21
|
-
border-radius: 20px; /* Rounded corners for the button */
|
|
22
|
-
background-color: #7E27ED; /* Background color from your style guide */
|
|
23
|
-
cursor: pointer;
|
|
24
|
-
box-shadow: 0 2px 10px rgba(0,0,0,0.2); /* Optional: add shadow for depth */
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#chat-icon img {
|
|
29
|
-
width: 80%; /* Adjust icon size as needed */
|
|
30
|
-
height: auto;
|
|
31
|
-
margin-right: 8px;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
#chat-icon span {
|
|
35
|
-
color: white; /* Text color */
|
|
36
|
-
font-family: 'Poppins', sans-serif; /* Font family from your style guide */
|
|
37
|
-
/* Add more text styles if needed */
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Chat window styles */
|
|
41
|
-
#chat-window {
|
|
42
|
-
width: 350px; /* Width of the chat window */
|
|
43
|
-
background-color: white;
|
|
44
|
-
border-radius: 8px;
|
|
45
|
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
|
46
|
-
position: absolute;
|
|
47
|
-
bottom: 80px; /* Positioning from the bottom */
|
|
48
|
-
right: 0;
|
|
49
|
-
display: none; /* Initially hidden */
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* Header of the chat window */
|
|
53
|
-
#chat-header {
|
|
54
|
-
background-color: #7E27ED;
|
|
55
|
-
color: white;
|
|
56
|
-
padding: 16px;
|
|
57
|
-
border-top-left-radius: 8px;
|
|
58
|
-
border-top-right-radius: 8px;
|
|
59
|
-
display: flex;
|
|
60
|
-
justify-content: space-between;
|
|
61
|
-
align-items: center;
|
|
62
|
-
position: relative; /* Needed for absolute positioning of the close button */
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/* Style for the close button */
|
|
67
|
-
#close-btn {
|
|
68
|
-
font-size: 24px; /* Size of the close button */
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
padding: 0 10px;
|
|
71
|
-
line-height: 24px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* Container for messages */
|
|
75
|
-
#message-container {
|
|
76
|
-
max-height: 300px;
|
|
77
|
-
overflow-y: auto;
|
|
78
|
-
padding: 15px;
|
|
79
|
-
background: #f7f7f7; /* Light grey background for the message area */
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* Input area */
|
|
83
|
-
#input-container {
|
|
84
|
-
border-top: 2px solid #ececec; /* Light border for input area */
|
|
85
|
-
background-color: white; /* White background for input area */
|
|
86
|
-
padding: 12px;
|
|
87
|
-
display: flex;
|
|
88
|
-
align-items: center; /* Align button and input vertically */
|
|
89
|
-
gap: 10px; /* Adds space between input and button */
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
#input-container input {
|
|
93
|
-
flex-grow: 1;
|
|
94
|
-
border: 2px solid #B928EB; /* Input border color based on style guide */
|
|
95
|
-
padding: 10px;
|
|
96
|
-
border-radius: 4px;
|
|
97
|
-
font-family: 'Poppins', sans-serif; /* Use Poppins font */
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#input-container button {
|
|
101
|
-
padding: 10px 20px; /* Larger padding for a larger button */
|
|
102
|
-
background-color: #5F82FF; /* Button background color from style guide */
|
|
103
|
-
border: none;
|
|
104
|
-
color: white;
|
|
105
|
-
border-radius: 4px;
|
|
106
|
-
font-family: 'Poppins', sans-serif; /* Use Poppins font */
|
|
107
|
-
font-size: 1rem; /* Adjust font size as needed */
|
|
108
|
-
cursor: pointer;
|
|
109
|
-
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Add subtle shadow for depth */
|
|
110
|
-
transition: background-color 0.3s ease; /* Smooth transition for background color */
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
#input-container button:hover {
|
|
114
|
-
background-color: #2FC0BF; /* Hover color based on style guide */
|
|
115
|
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Darker shadow for hover state */
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* Message bubble styling */
|
|
119
|
-
.message {
|
|
120
|
-
margin-bottom: 8px;
|
|
121
|
-
padding: 12px;
|
|
122
|
-
background-color: #e0e0e0; /* Light grey for message bubbles */
|
|
123
|
-
border-radius: 16px;
|
|
124
|
-
font-size: 14px;
|
|
125
|
-
line-height: 1.4;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.message.user {
|
|
129
|
-
background-color: #B928EB; /* User message bubble color */
|
|
130
|
-
color: white;
|
|
131
|
-
align-self: flex-end;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
.message.api {
|
|
135
|
-
background-color: #e0e0e0; /* API message bubble color */
|
|
136
|
-
color: black;
|
|
137
|
-
align-self: flex-start;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/* Wine item styling */
|
|
141
|
-
.wine-item {
|
|
142
|
-
display: flex;
|
|
143
|
-
align-items: center;
|
|
144
|
-
border: 1px solid #ddd;
|
|
145
|
-
border-radius: 8px;
|
|
146
|
-
padding: 10px;
|
|
147
|
-
margin-top: 10px;
|
|
148
|
-
background: white; /* White background for wine items */
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.wine-item img {
|
|
152
|
-
width: 80px; /* Smaller wine image size */
|
|
153
|
-
height: auto;
|
|
154
|
-
border-radius: 4px; /* Rounded corners for images */
|
|
155
|
-
margin-right: 12px;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.wine-item div {
|
|
159
|
-
font-size: 0.9rem; /* Smaller font size for wine info */
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/* Ensure the chat window is visible when the 'visible' class is added */
|
|
163
|
-
#chat-window.visible {
|
|
164
|
-
display: block;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/* Loader Styles */
|
|
168
|
-
.loader {
|
|
169
|
-
display: none; /* Loader is hidden by default */
|
|
170
|
-
border: 4px solid #f3f3f3; /* Light grey */
|
|
171
|
-
border-top: 4px solid #3498db; /* Blue, could be your primary color */
|
|
172
|
-
border-radius: 50%;
|
|
173
|
-
width: 40px;
|
|
174
|
-
height: 40px;
|
|
175
|
-
animation: spin 2s linear infinite;
|
|
176
|
-
margin: 20px auto; /* Centered horizontally */
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.loader.visible {
|
|
180
|
-
display: block;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
@keyframes spin {
|
|
184
|
-
0% { transform: rotate(0deg); }
|
|
185
|
-
100% { transform: rotate(360deg); }
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/* Typing indicator styles */
|
|
189
|
-
.typing-indicator {
|
|
190
|
-
display: flex;
|
|
191
|
-
justify-content: center;
|
|
192
|
-
align-items: center;
|
|
193
|
-
height: 0; /* Adjust as needed */
|
|
194
|
-
visibility: hidden; /* Start off invisible */
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.typing-indicator span {
|
|
198
|
-
display: block;
|
|
199
|
-
width: 8px;
|
|
200
|
-
height: 8px;
|
|
201
|
-
margin: 0 4px;
|
|
202
|
-
background-color: #3498db; /* Or use a color from your style guide */
|
|
203
|
-
border-radius: 50%;
|
|
204
|
-
opacity: 0.4;
|
|
205
|
-
animation: typing 1.5s infinite;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/* Animation keyframes */
|
|
209
|
-
@keyframes typing {
|
|
210
|
-
0% { transform: translateY(0); }
|
|
211
|
-
20% { transform: translateY(-10px); }
|
|
212
|
-
40% { transform: translateY(0); }
|
|
213
|
-
60% { transform: translateY(-10px); }
|
|
214
|
-
80% { transform: translateY(0); }
|
|
215
|
-
100% { transform: translateY(0); }
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/* To stagger the animation for each dot */
|
|
219
|
-
.typing-indicator span:nth-child(1) {
|
|
220
|
-
animation-delay: 0s;
|
|
221
|
-
}
|
|
222
|
-
.typing-indicator span:nth-child(2) {
|
|
223
|
-
animation-delay: 0.2s;
|
|
224
|
-
}
|
|
225
|
-
.typing-indicator span:nth-child(3) {
|
|
226
|
-
animation-delay: 0.4s;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/* Show the typing indicator */
|
|
230
|
-
.typing-indicator.visible {
|
|
231
|
-
visibility: visible;
|
|
232
|
-
height: 50px;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
#message-container {
|
|
236
|
-
overflow-y: auto;
|
|
237
|
-
height: 300px; /* adjust based on your UI needs */
|
|
238
|
-
padding: 10px;
|
|
239
|
-
}
|