Kgshop commited on
Commit
ab10c27
·
verified ·
1 Parent(s): 1147ec2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +406 -178
app.py CHANGED
@@ -369,6 +369,397 @@ def generate_chat_response(message, chat_history_from_client):
369
  else:
370
  return f"Извините, произошла ошибка: {e}"
371
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  CATALOG_TEMPLATE = '''
373
  <!DOCTYPE html>
374
  <html lang="ru">
@@ -552,6 +943,7 @@ CATALOG_TEMPLATE = '''
552
  justify-content: center;
553
  box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4);
554
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
555
  }
556
  .floating-button:hover {
557
  background-color: var(--accent-hover);
@@ -600,27 +992,6 @@ CATALOG_TEMPLATE = '''
600
  .formulate-order-button:hover { background-color: var(--accent-hover); }
601
  .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--accent); color: var(--bg-dark); padding: 10px 20px; border-radius: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); z-index: 1002; opacity: 0; transition: opacity 0.5s ease; font-size: 0.9rem;}
602
  .notification.show { opacity: 1;}
603
- #chatModal .modal-content { max-width: 450px; }
604
- #chat-messages { height: 350px; overflow-y: auto; border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; margin-bottom: 15px; display: flex; flex-direction: column; gap: 10px; background-color: #fcfcfc; }
605
- .chat-message { padding: 10px 15px; border-radius: 15px; max-width: 80%; word-wrap: break-word; line-height: 1.4;}
606
- .chat-message.user { align-self: flex-end; background-color: var(--bg-medium); color: white; border-bottom-right-radius: 2px; }
607
- .chat-message.ai { align-self: flex-start; background-color: #e6e6e6; color: var(--text-dark); border-bottom-left-radius: 2px; }
608
- .chat-input-container { display: flex; gap: 10px; }
609
- #chat-input { flex-grow: 1; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 20px; font-size: 0.95rem; outline: none; }
610
- #chat-input:focus { border-color: var(--bg-medium); box-shadow: 0 0 0 2px rgba(19, 93, 102, 0.15); }
611
- #chat-send-button { background-color: var(--bg-medium); color: white; border: none; border-radius: 50%; width: 40px; height: 40px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: background-color 0.3s; flex-shrink: 0; }
612
- #chat-send-button:hover { background-color: var(--bg-dark); }
613
- #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
614
-
615
- .chat-product-card { background-color: #f0f2f5; border-radius: 12px; padding: 10px; margin-top: 8px; display: flex; align-items: center; gap: 12px; border: 1px solid #e0e0e0; }
616
- .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
617
- .chat-product-card-info { flex-grow: 1; }
618
- .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
619
- .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
620
- .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
621
- .chat-product-link, .chat-add-to-cart { display: inline-block; background-color: #E0F2F1; color: var(--bg-medium); padding: 5px 10px; border-radius: 15px; cursor: pointer; font-size: 0.85rem; text-decoration: none; transition: background-color 0.2s; font-weight: 500; text-align: center; width: 100%; }
622
- .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
623
- .chat-product-card-actions .fa-cart-plus { font-size: 0.9em; }
624
 
625
  </style>
626
  </head>
@@ -720,23 +1091,10 @@ CATALOG_TEMPLATE = '''
720
  </div>
721
  </div>
722
 
723
- <div id="chatModal" class="modal">
724
- <div class="modal-content">
725
- <span class="close" onclick="closeModal('chatModal')" aria-label="Закрыть">×</span>
726
- <h2><i class="fas fa-comment-dots"></i> Чат с EVA</h2>
727
- <div id="chat-messages"></div>
728
- <div class="chat-input-container">
729
- <input type="text" id="chat-input" placeholder="Напишите сообщение...">
730
- <button id="chat-send-button"><i class="fas fa-paper-plane"></i></button>
731
- </div>
732
- <button id="clear-chat-button" class="product-button" style="background-color: var(--danger); margin-top: 15px;"><i class="fas fa-trash"></i> Очистить чат</button>
733
- </div>
734
- </div>
735
-
736
  <div class="floating-buttons-container">
737
- <button id="chat-open-button" class="floating-button" onclick="openChatModal()" aria-label="Открыть чат">
738
  <i class="fas fa-comment-dots"></i>
739
- </button>
740
  <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
741
  <i class="fas fa-shopping-cart"></i>
742
  <span id="cart-count">0</span>
@@ -752,7 +1110,6 @@ CATALOG_TEMPLATE = '''
752
  const currencyCode = '{{ currency_code }}';
753
  let selectedProductId = null;
754
  let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
755
- let chatHistory = JSON.parse(localStorage.getItem('evaChatHistory') || '[]');
756
 
757
  function getProductById(productId) {
758
  return allProducts.find(p => p.product_id === productId);
@@ -1076,150 +1433,9 @@ CATALOG_TEMPLATE = '''
1076
  }, duration);
1077
  }
1078
 
1079
- function openChatModal() {
1080
- const modal = document.getElementById('chatModal');
1081
- if(modal) {
1082
- modal.style.display = "block";
1083
- document.body.style.overflow = 'hidden';
1084
- displayChatHistory();
1085
- document.getElementById('chat-input').focus();
1086
- }
1087
- }
1088
-
1089
- function displayChatHistory() {
1090
- const chatMessagesDiv = document.getElementById('chat-messages');
1091
- chatMessagesDiv.innerHTML = '';
1092
- chatHistory.forEach(msg => {
1093
- addMessageToChat(msg.text, msg.role, false);
1094
- });
1095
- chatMessagesDiv.scrollTop = chatMessagesDiv.scrollHeight;
1096
- }
1097
-
1098
- function addMessageToChat(text, role, save = true) {
1099
- const chatMessagesDiv = document.getElementById('chat-messages');
1100
- const messageElement = document.createElement('div');
1101
- messageElement.className = `chat-message ${role}`;
1102
-
1103
- const productMatchRegex = /\[ID_ТОВАРА:\s*([a-fA-F0-9]+)\s*Название:\s*([^\]]+)\]/g;
1104
- let lastIndex = 0;
1105
- const contentFragment = document.createDocumentFragment();
1106
- let match;
1107
-
1108
- while ((match = productMatchRegex.exec(text)) !== null) {
1109
- if (match.index > lastIndex) {
1110
- const textPart = document.createElement('span');
1111
- textPart.innerHTML = text.substring(lastIndex, match.index).replace(/\\n/g, '<br>');
1112
- contentFragment.appendChild(textPart);
1113
- }
1114
-
1115
- const productId = match[1];
1116
- const product = getProductById(productId);
1117
-
1118
- if (product) {
1119
- const photoUrl = product.photos && product.photos.length > 0
1120
- ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${product.photos[0]}`
1121
- : 'https://via.placeholder.com/50x50.png?text=Gippo312';
1122
-
1123
- const card = document.createElement('div');
1124
- card.className = 'chat-product-card';
1125
- card.innerHTML = `
1126
- <img src="${photoUrl}" alt="${product.name}">
1127
- <div class="chat-product-card-info">
1128
- <strong>${product.name}</strong>
1129
- <span>${product.price.toFixed(2)} ${currencyCode}</span>
1130
- </div>
1131
- <div class="chat-product-card-actions">
1132
- <a href="#" class="chat-product-link" data-product-id="${productId}">Обзор</a>
1133
- <a href="#" class="chat-add-to-cart" data-product-id="${productId}"><i class="fas fa-cart-plus"></i></a>
1134
- </div>
1135
- `;
1136
- contentFragment.appendChild(card);
1137
- } else {
1138
- const productName = match[2];
1139
- const notFoundText = document.createElement('span');
1140
- notFoundText.innerHTML = `[ID_ТОВАРА: ${productId} Название: ${productName}] (товар не найден) `;
1141
- contentFragment.appendChild(notFoundText);
1142
- }
1143
- lastIndex = match.index + match[0].length;
1144
- }
1145
-
1146
- if (lastIndex < text.length) {
1147
- const textPart = document.createElement('span');
1148
- textPart.innerHTML = text.substring(lastIndex).replace(/\\n/g, '<br>');
1149
- contentFragment.appendChild(textPart);
1150
- }
1151
-
1152
- messageElement.appendChild(contentFragment);
1153
- chatMessagesDiv.appendChild(messageElement);
1154
- chatMessagesDiv.scrollTop = chatMessagesDiv.scrollHeight;
1155
-
1156
- if (save) {
1157
- chatHistory.push({ text: text, role: role });
1158
- localStorage.setItem('evaChatHistory', JSON.stringify(chatHistory));
1159
- }
1160
-
1161
- messageElement.querySelectorAll('.chat-product-link').forEach(link => {
1162
- link.addEventListener('click', (e) => {
1163
- e.preventDefault();
1164
- const id = e.currentTarget.dataset.productId;
1165
- closeModal('chatModal');
1166
- openModalById(id);
1167
- });
1168
- });
1169
- messageElement.querySelectorAll('.chat-add-to-cart').forEach(link => {
1170
- link.addEventListener('click', (e) => {
1171
- e.preventDefault();
1172
- const id = e.currentTarget.dataset.productId;
1173
- closeModal('chatModal');
1174
- openQuantityModalById(id);
1175
- });
1176
- });
1177
- }
1178
-
1179
- async function sendMessage() {
1180
- const chatInput = document.getElementById('chat-input');
1181
- const chatSendButton = document.getElementById('chat-send-button');
1182
- const message = chatInput.value.trim();
1183
- if (!message) return;
1184
- addMessageToChat(message, 'user');
1185
- chatInput.value = '';
1186
- chatSendButton.disabled = true;
1187
- try {
1188
- const response = await fetch('/chat_with_ai', {
1189
- method: 'POST',
1190
- headers: { 'Content-Type': 'application/json' },
1191
- body: JSON.stringify({ message: message, history: chatHistory })
1192
- });
1193
- const result = await response.json();
1194
- if (!response.ok) {
1195
- throw new Error(result.error || 'Ошибка при получении ответа от ИИ.');
1196
- }
1197
- addMessageToChat(result.text, 'ai');
1198
- } catch (error) {
1199
- console.error("Chat AI Error:", error);
1200
- addMessageToChat(`Извините, произошла ошибка: ${error.message}`, 'ai', false);
1201
- } finally {
1202
- chatSendButton.disabled = false;
1203
- }
1204
- }
1205
-
1206
- function clearChatHistory() {
1207
- if (confirm("Вы уверены, что хотите очистить историю чата?")) {
1208
- chatHistory = [];
1209
- localStorage.removeItem('evaChatHistory');
1210
- displayChatHistory();
1211
- showNotification("История чата очищена.");
1212
- }
1213
- }
1214
-
1215
  document.addEventListener('DOMContentLoaded', () => {
1216
  updateCartButton();
1217
  document.getElementById('search-input').addEventListener('input', filterProducts);
1218
- document.getElementById('chat-send-button').addEventListener('click', sendMessage);
1219
- document.getElementById('chat-input').addEventListener('keypress', function(e) {
1220
- if (e.key === 'Enter') { sendMessage(); }
1221
- });
1222
- document.getElementById('clear-chat-button').addEventListener('click', clearChatHistory);
1223
  window.addEventListener('click', function(event) {
1224
  if (event.target.classList.contains('modal')) { closeModal(event.target.id); }
1225
  });
@@ -2038,6 +2254,18 @@ def view_order(order_id):
2038
  currency_code=CURRENCY_CODE,
2039
  whatsapp_number=WHATSAPP_NUMBER)
2040
 
 
 
 
 
 
 
 
 
 
 
 
 
2041
 
2042
  @app.route('/admin', methods=['GET', 'POST'])
2043
  def admin():
 
369
  else:
370
  return f"Извините, произошла ошибка: {e}"
371
 
372
+ CHAT_TEMPLATE = '''
373
+ <!DOCTYPE html>
374
+ <html lang="ru">
375
+ <head>
376
+ <meta charset="UTF-8">
377
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
378
+ <title>EVA AI - Gippo312</title>
379
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
380
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
381
+ <style>
382
+ :root {
383
+ --bg-dark: #002b30;
384
+ --bg-gradient: linear-gradient(135deg, #003C43, #002b30);
385
+ --bg-message-user: linear-gradient(135deg, #48D1CC, #135D66);
386
+ --bg-message-ai: rgba(255, 255, 255, 0.95);
387
+ --text-user: #002b30;
388
+ --text-ai: #1a1a1a;
389
+ --accent: #48D1CC;
390
+ --glass: rgba(255, 255, 255, 0.1);
391
+ --border-glass: rgba(255, 255, 255, 0.15);
392
+ }
393
+ * { margin: 0; padding: 0; box-sizing: border-box; }
394
+ body {
395
+ font-family: 'Montserrat', sans-serif;
396
+ background: var(--bg-gradient);
397
+ color: #fff;
398
+ height: 100vh;
399
+ display: flex;
400
+ flex-direction: column;
401
+ overflow: hidden;
402
+ }
403
+ .chat-header {
404
+ padding: 15px 20px;
405
+ background: rgba(0, 60, 67, 0.9);
406
+ backdrop-filter: blur(15px);
407
+ border-bottom: 1px solid var(--border-glass);
408
+ display: flex;
409
+ align-items: center;
410
+ justify-content: space-between;
411
+ z-index: 100;
412
+ box-shadow: 0 4px 20px rgba(0,0,0,0.2);
413
+ }
414
+ .header-info { display: flex; align-items: center; gap: 12px; }
415
+ .bot-avatar {
416
+ width: 45px; height: 45px;
417
+ border-radius: 50%;
418
+ border: 2px solid var(--accent);
419
+ padding: 2px;
420
+ background: #fff;
421
+ object-fit: cover;
422
+ }
423
+ .header-text h1 { font-size: 1.1rem; font-weight: 700; color: #fff; }
424
+ .header-text p { font-size: 0.8rem; color: var(--accent); opacity: 0.9; }
425
+ .header-actions { display: flex; gap: 15px; }
426
+ .icon-btn {
427
+ background: none; border: none; color: #fff; font-size: 1.2rem; cursor: pointer;
428
+ transition: transform 0.2s, color 0.2s;
429
+ }
430
+ .icon-btn:hover { color: var(--accent); transform: scale(1.1); }
431
+
432
+ .chat-container {
433
+ flex: 1;
434
+ overflow-y: auto;
435
+ padding: 20px;
436
+ padding-bottom: 90px;
437
+ display: flex;
438
+ flex-direction: column;
439
+ gap: 20px;
440
+ scroll-behavior: smooth;
441
+ }
442
+ .chat-container::-webkit-scrollbar { width: 6px; }
443
+ .chat-container::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 10px; }
444
+
445
+ .message-row {
446
+ display: flex;
447
+ width: 100%;
448
+ opacity: 0;
449
+ transform: translateY(20px);
450
+ animation: popIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
451
+ }
452
+ .message-row.user { justify-content: flex-end; }
453
+ .message-row.ai { justify-content: flex-start; }
454
+
455
+ @keyframes popIn {
456
+ to { opacity: 1; transform: translateY(0); }
457
+ }
458
+
459
+ .message-bubble {
460
+ max-width: 80%;
461
+ padding: 14px 18px;
462
+ border-radius: 20px;
463
+ font-size: 0.95rem;
464
+ line-height: 1.5;
465
+ position: relative;
466
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
467
+ }
468
+ .user .message-bubble {
469
+ background: var(--bg-message-user);
470
+ color: var(--text-user);
471
+ border-bottom-right-radius: 4px;
472
+ }
473
+ .ai .message-bubble {
474
+ background: var(--bg-message-ai);
475
+ color: var(--text-ai);
476
+ border-bottom-left-radius: 4px;
477
+ }
478
+
479
+ /* Product Card inside Chat */
480
+ .chat-product-card {
481
+ background: #f8f9fa;
482
+ border-radius: 12px;
483
+ padding: 10px;
484
+ margin-top: 10px;
485
+ display: flex;
486
+ gap: 12px;
487
+ border: 1px solid #eee;
488
+ align-items: center;
489
+ }
490
+ .chat-product-card img {
491
+ width: 55px; height: 55px;
492
+ border-radius: 8px;
493
+ object-fit: cover;
494
+ border: 1px solid #ddd;
495
+ }
496
+ .product-info { flex: 1; min-width: 0; }
497
+ .product-info h4 { font-size: 0.9rem; margin-bottom: 2px; color: #333; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
498
+ .product-info .price { font-weight: 700; color: #135D66; font-size: 0.9rem; }
499
+ .product-actions { display: flex; gap: 8px; flex-direction: column; }
500
+ .p-btn {
501
+ background: #135D66; color: white; border: none;
502
+ padding: 6px 12px; border-radius: 20px; font-size: 0.75rem;
503
+ cursor: pointer; text-decoration: none; text-align: center;
504
+ transition: opacity 0.2s;
505
+ }
506
+ .p-btn:hover { opacity: 0.9; }
507
+
508
+ .typing-indicator {
509
+ display: none;
510
+ align-items: center;
511
+ gap: 4px;
512
+ padding: 12px 16px;
513
+ background: var(--bg-message-ai);
514
+ border-radius: 20px;
515
+ border-bottom-left-radius: 4px;
516
+ width: fit-content;
517
+ margin-bottom: 20px;
518
+ }
519
+ .dot {
520
+ width: 8px; height: 8px;
521
+ background: #888;
522
+ border-radius: 50%;
523
+ animation: bounce 1.4s infinite ease-in-out both;
524
+ }
525
+ .dot:nth-child(1) { animation-delay: -0.32s; }
526
+ .dot:nth-child(2) { animation-delay: -0.16s; }
527
+ @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } }
528
+
529
+ .input-area {
530
+ position: fixed;
531
+ bottom: 0; left: 0; right: 0;
532
+ padding: 15px 20px;
533
+ background: rgba(0, 43, 48, 0.85);
534
+ backdrop-filter: blur(20px);
535
+ border-top: 1px solid var(--border-glass);
536
+ display: flex;
537
+ gap: 12px;
538
+ align-items: center;
539
+ z-index: 101;
540
+ }
541
+ #message-input {
542
+ flex: 1;
543
+ background: rgba(255,255,255,0.1);
544
+ border: 1px solid var(--border-glass);
545
+ padding: 14px 20px;
546
+ border-radius: 30px;
547
+ color: #fff;
548
+ font-size: 1rem;
549
+ outline: none;
550
+ transition: all 0.3s;
551
+ }
552
+ #message-input:focus {
553
+ background: rgba(255,255,255,0.15);
554
+ border-color: var(--accent);
555
+ box-shadow: 0 0 15px rgba(72, 209, 204, 0.2);
556
+ }
557
+ .send-btn {
558
+ width: 50px; height: 50px;
559
+ border-radius: 50%;
560
+ border: none;
561
+ background: var(--accent);
562
+ color: var(--bg-dark);
563
+ font-size: 1.2rem;
564
+ cursor: pointer;
565
+ display: flex; justify-content: center; align-items: center;
566
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
567
+ }
568
+ .send-btn:hover { transform: scale(1.1) rotate(-10deg); box-shadow: 0 0 20px var(--accent); }
569
+ .send-btn:disabled { background: #555; cursor: not-allowed; transform: none; box-shadow: none; }
570
+
571
+ @media (min-width: 768px) {
572
+ .chat-container { max-width: 900px; margin: 0 auto; width: 100%; border-left: 1px solid var(--border-glass); border-right: 1px solid var(--border-glass); }
573
+ .input-area { max-width: 900px; margin: 0 auto; border-radius: 20px 20px 0 0; }
574
+ }
575
+ </style>
576
+ </head>
577
+ <body>
578
+ <div class="chat-header">
579
+ <div class="header-info">
580
+ <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" class="bot-avatar" alt="EVA">
581
+ <div class="header-text">
582
+ <h1>EVA AI</h1>
583
+ <p>Онлайн • Gippo312</p>
584
+ </div>
585
+ </div>
586
+ <div class="header-actions">
587
+ <button class="icon-btn" onclick="clearHistory()" title="Очистить чат"><i class="fas fa-trash-alt"></i></button>
588
+ <a href="/" class="icon-btn" title="Вернуться в магазин"><i class="fas fa-store"></i></a>
589
+ </div>
590
+ </div>
591
+
592
+ <div class="chat-container" id="chat-container">
593
+ <!-- Messages will appear here -->
594
+ <div class="message-row ai">
595
+ <div class="message-bubble">
596
+ Привет! Я EVA, ваш персональный помощник в Gippo312. Чем я могу помочь вам сегодня? 🛍️
597
+ </div>
598
+ </div>
599
+ </div>
600
+
601
+ <div class="typing-indicator" id="typing-indicator">
602
+ <div class="dot"></div><div class="dot"></div><div class="dot"></div>
603
+ </div>
604
+
605
+ <div class="input-area">
606
+ <input type="text" id="message-input" placeholder="Напишите сообщение..." autocomplete="off">
607
+ <button class="send-btn" id="send-btn"><i class="fas fa-paper-plane"></i></button>
608
+ </div>
609
+
610
+ <script>
611
+ const allProducts = {{ products_json|safe }};
612
+ const repoId = '{{ repo_id }}';
613
+ const currencyCode = '{{ currency_code }}';
614
+ const chatContainer = document.getElementById('chat-container');
615
+ const inputField = document.getElementById('message-input');
616
+ const sendBtn = document.getElementById('send-btn');
617
+ const typingIndicator = document.getElementById('typing-indicator');
618
+
619
+ let chatHistory = JSON.parse(localStorage.getItem('evaChatHistory') || '[]');
620
+
621
+ function getProductById(productId) {
622
+ return allProducts.find(p => p.product_id === productId);
623
+ }
624
+
625
+ function initChat() {
626
+ if (chatHistory.length > 0) {
627
+ chatContainer.innerHTML = '';
628
+ chatHistory.forEach(msg => appendMessage(msg.text, msg.role, false));
629
+ } else {
630
+ // Keep default welcome message if empty
631
+ }
632
+ scrollToBottom();
633
+ }
634
+
635
+ function scrollToBottom() {
636
+ chatContainer.scrollTop = chatContainer.scrollHeight;
637
+ }
638
+
639
+ function appendMessage(text, role, save = true) {
640
+ const row = document.createElement('div');
641
+ row.className = `message-row ${role}`;
642
+
643
+ const bubble = document.createElement('div');
644
+ bubble.className = 'message-bubble';
645
+
646
+ // Format text and products
647
+ const productMatchRegex = /\[ID_ТОВАРА:\s*([a-fA-F0-9]+)\s*Название:\s*([^\]]+)\]/g;
648
+ let lastIndex = 0;
649
+ let match;
650
+ const fragment = document.createDocumentFragment();
651
+
652
+ while ((match = productMatchRegex.exec(text)) !== null) {
653
+ if (match.index > lastIndex) {
654
+ const span = document.createElement('span');
655
+ span.innerHTML = text.substring(lastIndex, match.index).replace(/\\n/g, '<br>');
656
+ fragment.appendChild(span);
657
+ }
658
+
659
+ const productId = match[1];
660
+ const product = getProductById(productId);
661
+
662
+ if (product) {
663
+ const photoUrl = product.photos && product.photos.length > 0
664
+ ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${product.photos[0]}`
665
+ : 'https://via.placeholder.com/60x60.png?text=N/A';
666
+
667
+ const card = document.createElement('div');
668
+ card.className = 'chat-product-card';
669
+ card.innerHTML = `
670
+ <img src="${photoUrl}" alt="${product.name}">
671
+ <div class="product-info">
672
+ <h4>${product.name}</h4>
673
+ <div class="price">${product.price.toFixed(2)} ${currencyCode}</div>
674
+ </div>
675
+ <div class="product-actions">
676
+ <a href="/product/${allProducts.indexOf(product)}" class="p-btn" target="_blank">Обзор</a>
677
+ </div>
678
+ `;
679
+ fragment.appendChild(card);
680
+ }
681
+ lastIndex = match.index + match[0].length;
682
+ }
683
+
684
+ if (lastIndex < text.length) {
685
+ const span = document.createElement('span');
686
+ span.innerHTML = text.substring(lastIndex).replace(/\\n/g, '<br>');
687
+ fragment.appendChild(span);
688
+ }
689
+
690
+ bubble.appendChild(fragment);
691
+ row.appendChild(bubble);
692
+
693
+ // Insert before typing indicator if it exists and is visible (logic handled by container append)
694
+ chatContainer.appendChild(row);
695
+ scrollToBottom();
696
+
697
+ if (save) {
698
+ chatHistory.push({ text: text, role: role });
699
+ localStorage.setItem('evaChatHistory', JSON.stringify(chatHistory));
700
+ }
701
+ }
702
+
703
+ async function sendMessage() {
704
+ const text = inputField.value.trim();
705
+ if (!text) return;
706
+
707
+ inputField.value = '';
708
+ appendMessage(text, 'user');
709
+
710
+ // Show typing indicator
711
+ typingIndicator.style.display = 'flex';
712
+ chatContainer.appendChild(typingIndicator); // Move to bottom
713
+ scrollToBottom();
714
+
715
+ sendBtn.disabled = true;
716
+
717
+ try {
718
+ const response = await fetch('/chat_with_ai', {
719
+ method: 'POST',
720
+ headers: { 'Content-Type': 'application/json' },
721
+ body: JSON.stringify({ message: text, history: chatHistory })
722
+ });
723
+ const result = await response.json();
724
+
725
+ typingIndicator.style.display = 'none';
726
+ if (!response.ok) throw new Error(result.error);
727
+
728
+ appendMessage(result.text, 'ai');
729
+ } catch (error) {
730
+ typingIndicator.style.display = 'none';
731
+ appendMessage(`Ошибка: ${error.message}`, 'ai', false);
732
+ } finally {
733
+ sendBtn.disabled = false;
734
+ inputField.focus();
735
+ }
736
+ }
737
+
738
+ function clearHistory() {
739
+ if(confirm('Очистить историю чата?')) {
740
+ localStorage.removeItem('evaChatHistory');
741
+ chatHistory = [];
742
+ chatContainer.innerHTML = `
743
+ <div class="message-row ai">
744
+ <div class="message-bubble">
745
+ История очищена. Я готова к новым вопросам! ✨
746
+ </div>
747
+ </div>`;
748
+ }
749
+ }
750
+
751
+ sendBtn.addEventListener('click', sendMessage);
752
+ inputField.addEventListener('keypress', (e) => {
753
+ if (e.key === 'Enter') sendMessage();
754
+ });
755
+
756
+ // Initialize
757
+ initChat();
758
+ </script>
759
+ </body>
760
+ </html>
761
+ '''
762
+
763
  CATALOG_TEMPLATE = '''
764
  <!DOCTYPE html>
765
  <html lang="ru">
 
943
  justify-content: center;
944
  box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4);
945
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
946
+ text-decoration: none;
947
  }
948
  .floating-button:hover {
949
  background-color: var(--accent-hover);
 
992
  .formulate-order-button:hover { background-color: var(--accent-hover); }
993
  .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--accent); color: var(--bg-dark); padding: 10px 20px; border-radius: 20px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); z-index: 1002; opacity: 0; transition: opacity 0.5s ease; font-size: 0.9rem;}
994
  .notification.show { opacity: 1;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
995
 
996
  </style>
997
  </head>
 
1091
  </div>
1092
  </div>
1093
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1094
  <div class="floating-buttons-container">
1095
+ <a href="/chat" class="floating-button" aria-label="Открыть чат">
1096
  <i class="fas fa-comment-dots"></i>
1097
+ </a>
1098
  <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
1099
  <i class="fas fa-shopping-cart"></i>
1100
  <span id="cart-count">0</span>
 
1110
  const currencyCode = '{{ currency_code }}';
1111
  let selectedProductId = null;
1112
  let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
 
1113
 
1114
  function getProductById(productId) {
1115
  return allProducts.find(p => p.product_id === productId);
 
1433
  }, duration);
1434
  }
1435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1436
  document.addEventListener('DOMContentLoaded', () => {
1437
  updateCartButton();
1438
  document.getElementById('search-input').addEventListener('input', filterProducts);
 
 
 
 
 
1439
  window.addEventListener('click', function(event) {
1440
  if (event.target.classList.contains('modal')) { closeModal(event.target.id); }
1441
  });
 
2254
  currency_code=CURRENCY_CODE,
2255
  whatsapp_number=WHATSAPP_NUMBER)
2256
 
2257
+ @app.route('/chat')
2258
+ def chat_page():
2259
+ data = load_data()
2260
+ all_products_raw = data.get('products', [])
2261
+ products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2262
+
2263
+ return render_template_string(
2264
+ CHAT_TEMPLATE,
2265
+ products_json=json.dumps(products_in_stock),
2266
+ repo_id=REPO_ID,
2267
+ currency_code=CURRENCY_CODE
2268
+ )
2269
 
2270
  @app.route('/admin', methods=['GET', 'POST'])
2271
  def admin():