Kgshop commited on
Commit
5e5f525
·
verified ·
1 Parent(s): 8ca375b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +736 -598
app.py CHANGED
@@ -32,7 +32,7 @@ HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
32
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
33
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
34
 
35
- STORE_ADDRESS = "Рынок Кербен, 6 ряд , 43 контейнер "
36
  WHATSAPP_NUMBER = "+996701202013"
37
 
38
  CURRENCY_CODE = 'KGS'
@@ -247,7 +247,7 @@ def generate_ai_description_from_image(image_data, language):
247
  final_prompt = f"{base_prompt}{lang_suffix}"
248
 
249
  try:
250
- model = genai.GenerativeModel('gemma-2-27b-it')
251
 
252
  response = model.generate_content([final_prompt, image])
253
 
@@ -266,7 +266,7 @@ def generate_ai_description_from_image(image_data, language):
266
  elif " Billing account not found" in str(e):
267
  raise ValueError("Проблема с биллингом аккаунта Google Cloud. Проверьте ваш аккаунт.")
268
  elif "Could not find model" in str(e):
269
- raise ValueError(f"Модель 'gemma-2-27b-it' не найдена или недоступна.")
270
  elif "resource has been exhausted" in str(e).lower():
271
  raise ValueError("Квота запросов исчерпана. Попробуйте позже.")
272
  elif "content has been blocked" in str(e).lower():
@@ -277,7 +277,7 @@ def generate_ai_description_from_image(image_data, language):
277
  else:
278
  raise ValueError(f"Ошибка при генерации контента: {e}")
279
 
280
- def generate_chat_response(message, chat_history_from_client, session_id):
281
  if not configure_gemini():
282
  return "Извините, сервис чата временно недоступен. Пожалуйста, попробуйте позже."
283
 
@@ -329,17 +329,12 @@ def generate_chat_response(message, chat_history_from_client, session_id):
329
  response = None
330
 
331
  try:
332
- model = genai.GenerativeModel('gemma-2-27b-it')
333
 
334
  model_chat_history_for_gemini = [
335
  {'role': 'user', 'parts': [{'text': system_instruction_content}]}
336
  ]
337
-
338
- chats_data = data.get('chats', {})
339
- if session_id not in chats_data:
340
- chats_data[session_id] = []
341
-
342
- for entry in chats_data[session_id]:
343
  gemini_role = 'model' if entry['role'] == 'ai' else 'user'
344
  model_chat_history_for_gemini.append({
345
  'role': gemini_role,
@@ -360,11 +355,6 @@ def generate_chat_response(message, chat_history_from_client, session_id):
360
  generated_text = response.text
361
  else:
362
  raise ValueError("AI did not return a valid text response.")
363
-
364
- chats_data[session_id].append({'role': 'user', 'text': message, 'timestamp': datetime.now().isoformat()})
365
- chats_data[session_id].append({'role': 'ai', 'text': generated_text, 'timestamp': datetime.now().isoformat()})
366
- data['chats'] = chats_data
367
- save_data(data)
368
 
369
  return generated_text
370
 
@@ -383,313 +373,6 @@ def generate_chat_response(message, chat_history_from_client, session_id):
383
  else:
384
  return f"Извините, произошла ошибка: {e}"
385
 
386
- CHAT_TEMPLATE = '''
387
- <!DOCTYPE html>
388
- <html lang="ru">
389
- <head>
390
- <meta charset="UTF-8">
391
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
392
- <title>Gippo312 - Чат с EVA</title>
393
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
394
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
395
- <style>
396
- :root {
397
- --bg-dark: #003C43;
398
- --bg-medium: #135D66;
399
- --accent: #48D1CC;
400
- --accent-hover: #77E4D8;
401
- --text-light: #E3FEF7;
402
- --text-dark: #333;
403
- --danger: #E57373;
404
- }
405
- * { margin: 0; padding: 0; box-sizing: border-box; }
406
- html, body { height: 100%; overflow: hidden; -webkit-tap-highlight-color: transparent; }
407
- body {
408
- font-family: 'Montserrat', sans-serif;
409
- background-color: var(--bg-dark);
410
- color: var(--text-light);
411
- display: flex;
412
- flex-direction: column;
413
- }
414
- .chat-container {
415
- display: flex;
416
- flex-direction: column;
417
- height: 100%;
418
- max-width: 800px;
419
- margin: 0 auto;
420
- width: 100%;
421
- background-color: #002B30;
422
- }
423
- .chat-header {
424
- display: flex;
425
- align-items: center;
426
- padding: 15px 20px;
427
- background-color: var(--bg-dark);
428
- border-bottom: 1px solid var(--bg-medium);
429
- flex-shrink: 0;
430
- }
431
- .chat-header img {
432
- width: 45px;
433
- height: 45px;
434
- border-radius: 50%;
435
- margin-right: 15px;
436
- border: 2px solid var(--accent);
437
- }
438
- .chat-header h1 {
439
- font-size: 1.2rem;
440
- font-weight: 600;
441
- }
442
- .chat-header .catalog-link {
443
- margin-left: auto;
444
- color: var(--accent);
445
- font-size: 1.5rem;
446
- text-decoration: none;
447
- }
448
- .chat-messages {
449
- flex-grow: 1;
450
- overflow-y: auto;
451
- padding: 20px;
452
- display: flex;
453
- flex-direction: column;
454
- gap: 12px;
455
- -webkit-overflow-scrolling: touch;
456
- }
457
- .chat-message {
458
- padding: 12px 18px;
459
- border-radius: 20px;
460
- max-width: 85%;
461
- word-wrap: break-word;
462
- line-height: 1.5;
463
- animation: fadeIn 0.3s ease-out;
464
- }
465
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
466
- .chat-message.user {
467
- align-self: flex-end;
468
- background-color: var(--bg-medium);
469
- color: white;
470
- border-bottom-right-radius: 4px;
471
- }
472
- .chat-message.ai {
473
- align-self: flex-start;
474
- background-color: #004D54;
475
- color: var(--text-light);
476
- border-bottom-left-radius: 4px;
477
- }
478
- .chat-input-container {
479
- display: flex;
480
- gap: 10px;
481
- padding: 15px;
482
- background-color: var(--bg-dark);
483
- border-top: 1px solid var(--bg-medium);
484
- flex-shrink: 0;
485
- }
486
- #chat-input {
487
- flex-grow: 1;
488
- padding: 12px 20px;
489
- border: none;
490
- border-radius: 25px;
491
- font-size: 1rem;
492
- outline: none;
493
- background-color: var(--bg-medium);
494
- color: var(--text-light);
495
- }
496
- #chat-input::placeholder { color: rgba(227, 254, 247, 0.6); }
497
- #chat-send-button {
498
- background-color: var(--accent);
499
- color: var(--bg-dark);
500
- border: none;
501
- border-radius: 50%;
502
- width: 48px;
503
- height: 48px;
504
- display: flex;
505
- justify-content: center;
506
- align-items: center;
507
- cursor: pointer;
508
- transition: background-color 0.3s;
509
- flex-shrink: 0;
510
- font-size: 1.2rem;
511
- }
512
- #chat-send-button:hover { background-color: var(--accent-hover); }
513
- #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
514
-
515
- .chat-product-card { background-color: #E3FEF7; border-radius: 12px; padding: 12px; margin-top: 10px; display: flex; align-items: center; gap: 15px; border: 1px solid var(--bg-medium); color: var(--text-dark); }
516
- .chat-product-card img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
517
- .chat-product-card-info { flex-grow: 1; }
518
- .chat-product-card-info strong { display: block; font-size: 1rem; color: var(--text-dark); margin-bottom: 4px; }
519
- .chat-product-card-info span { font-size: 0.95rem; color: var(--bg-medium); font-weight: 600; }
520
- .chat-product-card-actions { display: flex; flex-direction: column; gap: 8px; }
521
- .chat-product-link, .chat-add-to-cart { display: flex; align-items: center; justify-content: center; background-color: #B2DFDB; color: var(--bg-medium); padding: 8px 12px; border-radius: 20px; cursor: pointer; font-size: 0.9rem; text-decoration: none; transition: background-color 0.2s; font-weight: 500; text-align: center; width: 100%; gap: 5px;}
522
- .chat-product-link:hover, .chat-add-to-cart:hover { background-color: var(--accent); }
523
- </style>
524
- </head>
525
- <body>
526
- <div class="chat-container">
527
- <div class="chat-header">
528
- <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Gippo312 Logo">
529
- <h1>Чат с EVA</h1>
530
- <a href="{{ url_for('catalog') }}" class="catalog-link"><i class="fas fa-store"></i></a>
531
- </div>
532
- <div id="chat-messages" class="chat-messages"></div>
533
- <div class="chat-input-container">
534
- <input type="text" id="chat-input" placeholder="Напишите сообщение...">
535
- <button id="chat-send-button"><i class="fas fa-paper-plane"></i></button>
536
- </div>
537
- </div>
538
- <script>
539
- const allProducts = {{ products_json|safe }};
540
- const repoId = '{{ repo_id }}';
541
- const currencyCode = '{{ currency_code }}';
542
- let chatHistory = JSON.parse(localStorage.getItem('evaChatHistory') || '[]');
543
- let sessionId = localStorage.getItem('chatSessionId');
544
- if (!sessionId) {
545
- sessionId = '{{ uuid4().hex }}';
546
- localStorage.setItem('chatSessionId', sessionId);
547
- }
548
-
549
- function getProductById(productId) {
550
- return allProducts.find(p => p.product_id === productId);
551
- }
552
-
553
- function displayChatHistory() {
554
- const chatMessagesDiv = document.getElementById('chat-messages');
555
- chatMessagesDiv.innerHTML = '';
556
- chatHistory.forEach(msg => addMessageToChat(msg.text, msg.role, false));
557
- }
558
-
559
- function addMessageToChat(text, role, save = true) {
560
- const chatMessagesDiv = document.getElementById('chat-messages');
561
- const messageElement = document.createElement('div');
562
- messageElement.className = `chat-message ${role}`;
563
-
564
- const productMatchRegex = /\[ID_ТОВАРА:\s*([a-fA-F0-9]+)\s*Название:\s*([^\]]+)\]/g;
565
- let lastIndex = 0;
566
- const contentFragment = document.createDocumentFragment();
567
- let match;
568
-
569
- while ((match = productMatchRegex.exec(text)) !== null) {
570
- if (match.index > lastIndex) {
571
- const textPart = document.createElement('span');
572
- textPart.innerHTML = text.substring(lastIndex, match.index).replace(/\\n/g, '<br>');
573
- contentFragment.appendChild(textPart);
574
- }
575
-
576
- const productId = match[1];
577
- const product = getProductById(productId);
578
-
579
- if (product) {
580
- const photoUrl = product.photos && product.photos.length > 0
581
- ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${product.photos[0]}`
582
- : 'https://via.placeholder.com/60x60.png?text=Gippo312';
583
-
584
- const card = document.createElement('div');
585
- card.className = 'chat-product-card';
586
- card.innerHTML = `
587
- <img src="${photoUrl}" alt="${product.name}">
588
- <div class="chat-product-card-info">
589
- <strong>${product.name}</strong>
590
- <span>${product.price.toFixed(2)} ${currencyCode}</span>
591
- </div>
592
- <div class="chat-product-card-actions">
593
- <a href="#" class="chat-add-to-cart" data-product-id="${productId}"><i class="fas fa-cart-plus"></i></a>
594
- </div>
595
- `;
596
- contentFragment.appendChild(card);
597
- } else {
598
- const productName = match[2];
599
- const notFoundText = document.createElement('span');
600
- notFoundText.innerHTML = `[ID_ТОВАРА: ${productId} Название: ${productName}] (товар не найден) `;
601
- contentFragment.appendChild(notFoundText);
602
- }
603
- lastIndex = match.index + match[0].length;
604
- }
605
-
606
- if (lastIndex < text.length) {
607
- const textPart = document.createElement('span');
608
- textPart.innerHTML = text.substring(lastIndex).replace(/\\n/g, '<br>');
609
- contentFragment.appendChild(textPart);
610
- }
611
-
612
- messageElement.appendChild(contentFragment);
613
- chatMessagesDiv.appendChild(messageElement);
614
- chatMessagesDiv.scrollTop = chatMessagesDiv.scrollHeight;
615
-
616
- if (save) {
617
- chatHistory.push({ text: text, role: role });
618
- localStorage.setItem('evaChatHistory', JSON.stringify(chatHistory));
619
- }
620
-
621
- messageElement.querySelectorAll('.chat-add-to-cart').forEach(link => {
622
- link.addEventListener('click', (e) => {
623
- e.preventDefault();
624
- const id = e.currentTarget.dataset.productId;
625
- const product = getProductById(id);
626
- if (product) {
627
- addToCart(product);
628
- }
629
- });
630
- });
631
- }
632
-
633
- function addToCart(product) {
634
- let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
635
- const cartItemId = `${product.product_id}-N/A`;
636
- const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
637
- if (existingItemIndex > -1) {
638
- cart[existingItemIndex].quantity += 1;
639
- } else {
640
- cart.push({
641
- id: cartItemId,
642
- product_id: product.product_id,
643
- name: product.name,
644
- price: product.price,
645
- photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
646
- quantity: 1,
647
- color: 'N/A'
648
- });
649
- }
650
- localStorage.setItem('mekaCart', JSON.stringify(cart));
651
- alert(`${product.name} добавлен в корзину!`);
652
- }
653
-
654
- async function sendMessage() {
655
- const chatInput = document.getElementById('chat-input');
656
- const chatSendButton = document.getElementById('chat-send-button');
657
- const message = chatInput.value.trim();
658
- if (!message) return;
659
- addMessageToChat(message, 'user');
660
- chatInput.value = '';
661
- chatSendButton.disabled = true;
662
- try {
663
- const response = await fetch('/chat_with_ai', {
664
- method: 'POST',
665
- headers: { 'Content-Type': 'application/json' },
666
- body: JSON.stringify({ message: message, history: chatHistory, session_id: sessionId })
667
- });
668
- const result = await response.json();
669
- if (!response.ok) {
670
- throw new Error(result.error || 'Ошибка при получении ответа от ИИ.');
671
- }
672
- addMessageToChat(result.text, 'ai');
673
- } catch (error) {
674
- console.error("Chat AI Error:", error);
675
- addMessageToChat(`Извините, произошла ошибка: ${error.message}`, 'ai', false);
676
- } finally {
677
- chatSendButton.disabled = false;
678
- }
679
- }
680
-
681
- document.addEventListener('DOMContentLoaded', () => {
682
- displayChatHistory();
683
- document.getElementById('chat-send-button').addEventListener('click', sendMessage);
684
- document.getElementById('chat-input').addEventListener('keypress', function(e) {
685
- if (e.key === 'Enter') { sendMessage(); }
686
- });
687
- });
688
- </script>
689
- </body>
690
- </html>
691
- '''
692
-
693
  CATALOG_TEMPLATE = '''
694
  <!DOCTYPE html>
695
  <html lang="ru">
@@ -873,6 +556,7 @@ CATALOG_TEMPLATE = '''
873
  justify-content: center;
874
  box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4);
875
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
876
  }
877
  .floating-button:hover {
878
  background-color: var(--accent-hover);
@@ -921,18 +605,6 @@ CATALOG_TEMPLATE = '''
921
  .formulate-order-button:hover { background-color: var(--accent-hover); }
922
  .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;}
923
  .notification.show { opacity: 1;}
924
- #chatModal .modal-content { max-width: 450px; }
925
- #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; }
926
- .chat-message { padding: 10px 15px; border-radius: 15px; max-width: 80%; word-wrap: break-word; line-height: 1.4;}
927
- .chat-message.user { align-self: flex-end; background-color: var(--bg-medium); color: white; border-bottom-right-radius: 2px; }
928
- .chat-message.ai { align-self: flex-start; background-color: #e6e6e6; color: var(--text-dark); border-bottom-left-radius: 2px; }
929
- .chat-input-container { display: flex; gap: 10px; }
930
- #chat-input { flex-grow: 1; padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 20px; font-size: 0.95rem; outline: none; }
931
- #chat-input:focus { border-color: var(--bg-medium); box-shadow: 0 0 0 2px rgba(19, 93, 102, 0.15); }
932
- #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; }
933
- #chat-send-button:hover { background-color: var(--bg-dark); }
934
- #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
935
-
936
  .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; }
937
  .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
938
  .chat-product-card-info { flex-grow: 1; }
@@ -943,32 +615,6 @@ CATALOG_TEMPLATE = '''
943
  .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
944
  .chat-product-card-actions .fa-cart-plus { font-size: 0.9em; }
945
 
946
- @media (max-width: 768px) {
947
- .product-card {
948
- width: 150px;
949
- height: 150px;
950
- }
951
- .category-header h2 {
952
- font-size: 1.3rem;
953
- }
954
- .floating-buttons-container {
955
- bottom: 15px;
956
- right: 15px;
957
- }
958
- .floating-button {
959
- width: 50px;
960
- height: 50px;
961
- font-size: 1.3rem;
962
- }
963
- .cart-item {
964
- grid-template-columns: 50px 1fr auto;
965
- gap: 10px;
966
- }
967
- .cart-item-details { grid-column: 2; }
968
- .cart-item-quantity { grid-column: 2; grid-row: 2; }
969
- .cart-item-total { grid-column: 3; grid-row: 1; }
970
- .cart-item-remove { grid-column: 3; grid-row: 2; }
971
- }
972
  </style>
973
  </head>
974
  <body>
@@ -1013,7 +659,7 @@ CATALOG_TEMPLATE = '''
1013
  {% endif %}
1014
  </div>
1015
  <div class="product-info-overlay">
1016
- <span class="product-price">{{ "%.2f"|format(product.price) }} {{ currency_code }}</span>
1017
  </div>
1018
  </div>
1019
  {% endfor %}
@@ -1068,7 +714,7 @@ CATALOG_TEMPLATE = '''
1068
  </div>
1069
 
1070
  <div class="floating-buttons-container">
1071
- <a href="{{ url_for('chat') }}" id="chat-open-button" class="floating-button" aria-label="Открыть чат">
1072
  <i class="fas fa-comment-dots"></i>
1073
  </a>
1074
  <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
@@ -1086,7 +732,7 @@ CATALOG_TEMPLATE = '''
1086
  const currencyCode = '{{ currency_code }}';
1087
  let selectedProductId = null;
1088
  let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
1089
-
1090
  function getProductById(productId) {
1091
  return allProducts.find(p => p.product_id === productId);
1092
  }
@@ -1237,7 +883,6 @@ CATALOG_TEMPLATE = '''
1237
  cartButton.style.display = 'flex';
1238
  } else {
1239
  cartCountElement.style.display = 'none';
1240
- cartButton.style.display = 'none';
1241
  }
1242
  }
1243
 
@@ -1274,154 +919,656 @@ CATALOG_TEMPLATE = '''
1274
  </div>
1275
  `;
1276
  }).join('');
1277
- cartTotalElement.textContent = total.toFixed(2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1278
  }
1279
- const modal = document.getElementById('cartModal');
1280
- if (modal) {
1281
- modal.style.display = "block";
1282
- document.body.style.overflow = 'hidden';
1283
- }
1284
  }
1285
-
1286
  function incrementCartItem(itemId) {
1287
- const itemIndex = cart.findIndex(item => item.id === itemId);
1288
- if (itemIndex > -1) {
1289
- cart[itemIndex].quantity++;
1290
- localStorage.setItem('mekaCart', JSON.stringify(cart));
1291
- openCartModal();
1292
- updateCartButton();
1293
- }
1294
  }
1295
-
1296
  function decrementCartItem(itemId) {
1297
- const itemIndex = cart.findIndex(item => item.id === itemId);
1298
  if (itemIndex > -1) {
1299
  cart[itemIndex].quantity--;
1300
- if (cart[itemIndex].quantity <= 0) {
1301
- cart.splice(itemIndex, 1);
1302
- }
1303
  localStorage.setItem('mekaCart', JSON.stringify(cart));
1304
  openCartModal();
1305
  updateCartButton();
1306
  }
1307
  }
1308
-
1309
  function removeFromCart(itemId) {
1310
  cart = cart.filter(item => item.id !== itemId);
1311
  localStorage.setItem('mekaCart', JSON.stringify(cart));
1312
  openCartModal();
1313
  updateCartButton();
1314
  }
1315
-
1316
  function clearCart() {
1317
- if (confirm("Вы уверены, что хотите очистить корзину?")) {
1318
  cart = [];
1319
  localStorage.removeItem('mekaCart');
1320
  openCartModal();
1321
  updateCartButton();
1322
  }
1323
  }
1324
-
1325
  function formulateOrder() {
1326
- if (cart.length === 0) {
1327
- alert("Корзина пуста! Добавьте товары перед формированием заказа.");
1328
- return;
1329
- }
1330
- const orderData = { cart: cart };
1331
- const formulateButton = document.querySelector('.formulate-order-button');
1332
- if (formulateButton) formulateButton.disabled = true;
1333
- showNotification("Формируем заказ...", 5000);
1334
- fetch('/create_order', {
1335
- method: 'POST',
1336
- headers: { 'Content-Type': 'application/json' },
1337
- body: JSON.stringify(orderData)
1338
- })
1339
- .then(response => {
1340
- if (!response.ok) {
1341
- return response.json().then(err => { throw new Error(err.error || 'Не удалось создать заказ'); });
1342
- }
1343
- return response.json();
1344
- })
1345
- .then(data => {
1346
- if (data.order_id) {
1347
- localStorage.removeItem('mekaCart');
1348
- cart = [];
1349
- updateCartButton();
1350
- closeModal('cartModal');
1351
- window.location.href = `/order/${data.order_id}`;
1352
- } else {
1353
- throw new Error('Не получен ID заказа от сервера.');
1354
- }
1355
- })
1356
- .catch(error => {
1357
- console.error('Ошибка при формировании заказа:', error);
1358
- alert(`Ошибка: ${error.message}`);
1359
- if (formulateButton) formulateButton.disabled = false;
1360
- });
1361
- }
1362
-
1363
- function filterProducts() {
1364
- const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
1365
- const allCategorySections = document.querySelectorAll('.category-section');
1366
- const noResultsEl = document.getElementById('no-results-message');
1367
- let totalResults = 0;
1368
-
1369
- allCategorySections.forEach(section => {
1370
- const productCards = section.querySelectorAll('.product-card');
1371
- let categoryHasVisibleProducts = false;
1372
- productCards.forEach(card => {
1373
- const name = card.dataset.name || '';
1374
- const description = card.dataset.description || '';
1375
- if (searchTerm === '' || name.includes(searchTerm) || description.includes(searchTerm)) {
1376
- card.style.display = 'inline-block';
1377
- categoryHasVisibleProducts = true;
1378
- } else {
1379
- card.style.display = 'none';
1380
  }
1381
  });
1382
- if (categoryHasVisibleProducts) {
1383
- section.style.display = 'block';
1384
- totalResults++;
1385
- } else {
1386
- section.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1387
  }
1388
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1389
 
1390
- if (totalResults === 0 && searchTerm !== '') {
1391
- if (noResultsEl) noResultsEl.style.display = 'block';
1392
- } else {
1393
- if (noResultsEl) noResultsEl.style.display = 'none';
 
1394
  }
 
 
 
1395
  }
1396
 
1397
- function showNotification(message, duration = 3000) {
1398
- const placeholder = document.getElementById('notification-placeholder');
1399
- if (!placeholder) return;
1400
- const notification = document.createElement('div');
1401
- notification.className = 'notification';
1402
- notification.textContent = message;
1403
- placeholder.appendChild(notification);
1404
- void notification.offsetWidth;
1405
- notification.classList.add('show');
1406
- setTimeout(() => {
1407
- notification.classList.remove('show');
1408
- notification.addEventListener('transitionend', () => notification.remove());
1409
- }, duration);
1410
- }
1411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1412
  document.addEventListener('DOMContentLoaded', () => {
1413
  updateCartButton();
1414
- document.getElementById('search-input').addEventListener('input', filterProducts);
1415
- window.addEventListener('click', function(event) {
1416
- if (event.target.classList.contains('modal')) { closeModal(event.target.id); }
1417
- });
1418
- window.addEventListener('keydown', function(event) {
1419
- if (event.key === 'Escape') {
1420
- document.querySelectorAll('.modal[style*="display: block"]').forEach(modal => {
1421
- closeModal(modal.id);
1422
- });
1423
- }
1424
- });
1425
  });
1426
  </script>
1427
  </body>
@@ -1457,7 +1604,7 @@ PRODUCT_DETAIL_TEMPLATE = '''
1457
  </div>
1458
 
1459
  <div style="text-align:center; margin-top:20px; padding: 0 10px;">
1460
- <p style="font-size: 1.5rem; font-weight: bold; color: #135D66; margin-bottom: 15px;"><strong>Цена:</strong> {{ "%.2f"|format(product.price) }} {{ currency_code }}</p>
1461
  <button class="product-button formulate-order-button" style="padding: 12px 30px; width: 100%; max-width: 300px;" onclick="closeModal('productModal'); openQuantityModalById('{{ product.get('product_id', '') }}')">
1462
  <i class="fas fa-cart-plus"></i> В корзину
1463
  </button>
@@ -1519,14 +1666,6 @@ ORDER_TEMPLATE = '''
1519
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--bg-medium); text-decoration: none; font-size: 0.9rem; }
1520
  .catalog-link:hover { text-decoration: underline; }
1521
  .not-found { text-align: center; color: #dc3545; font-size: 1.2rem; padding: 40px 0;}
1522
- @media (max-width: 600px) {
1523
- .container { padding: 20px; }
1524
- h1 { font-size: 1.5rem; }
1525
- .order-item { grid-template-columns: 50px 1fr auto; }
1526
- .item-details { grid-column: 2; grid-row: 1; }
1527
- .item-quantity { grid-column: 2; grid-row: 2; justify-self: start; }
1528
- .item-total { grid-column: 3; grid-row: 1 / span 2; }
1529
- }
1530
  </style>
1531
  </head>
1532
  <body>
@@ -1732,44 +1871,16 @@ ADMIN_TEMPLATE = '''
1732
  .status-indicator.top-product { background-color: #FFF9C4; color: #F57F17; margin-left: 5px;}
1733
  .ai-generate-button { background-color: #8D6EC8; color: white; margin-top: 5px; margin-bottom: 10px; }
1734
  .ai-generate-button:hover { background-color: #7B4DB5; }
1735
- .chat-log-modal {
1736
- display: none;
1737
- position: fixed;
1738
- z-index: 1002;
1739
- left: 0;
1740
- top: 0;
1741
- width: 100%;
1742
- height: 100%;
1743
- overflow: auto;
1744
- background-color: rgba(0,0,0,0.5);
1745
- }
1746
- .chat-log-modal-content {
1747
- background-color: #fefefe;
1748
- margin: 5% auto;
1749
- padding: 20px;
1750
- border: 1px solid #888;
1751
- width: 80%;
1752
- max-width: 800px;
1753
- border-radius: 10px;
1754
- }
1755
- .chat-log-message {
1756
- padding: 8px;
1757
- margin-bottom: 8px;
1758
- border-radius: 5px;
1759
- }
1760
- .chat-log-message.user {
1761
- background-color: #e1f5fe;
1762
- text-align: right;
1763
- }
1764
- .chat-log-message.ai {
1765
- background-color: #f1f8e9;
1766
- }
1767
- .chat-session-item {
1768
- cursor: pointer;
1769
- }
1770
- .chat-session-item:hover {
1771
- background-color: #f0f0f0;
1772
- }
1773
  </style>
1774
  </head>
1775
  <body>
@@ -1789,21 +1900,6 @@ ADMIN_TEMPLATE = '''
1789
  {% endfor %}
1790
  {% endif %}
1791
  {% endwith %}
1792
-
1793
- <div class="section">
1794
- <h2><i class="fas fa-comments"></i> Диалоги с EVA</h2>
1795
- <div class="item-list">
1796
- {% for session_id, messages in chats.items() %}
1797
- <div class="item chat-session-item" onclick="openChatLogModal('{{ session_id }}')">
1798
- <p><strong>ID сессии:</strong> {{ session_id }}</p>
1799
- <p><strong>Сообщений:</strong> {{ messages|length }}</p>
1800
- <p><strong>Последнее сообщение:</strong> {{ messages[-1]['timestamp'] }}</p>
1801
- </div>
1802
- {% else %}
1803
- <p>Нет сохраненных диалогов.</p>
1804
- {% endfor %}
1805
- </div>
1806
- </div>
1807
 
1808
  <div class="section">
1809
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
@@ -1818,6 +1914,23 @@ ADMIN_TEMPLATE = '''
1818
  <p style="font-size: 0.85rem; color: #999;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
1819
  </div>
1820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1821
  <div class="flex-container">
1822
  <div class="flex-item">
1823
  <div class="section">
@@ -2055,14 +2168,12 @@ ADMIN_TEMPLATE = '''
2055
  <p>Товаров пока нет.</p>
2056
  {% endif %}
2057
  </div>
2058
-
2059
  </div>
2060
-
2061
- <div id="chatLogModal" class="chat-log-modal">
2062
- <div class="chat-log-modal-content">
2063
- <span class="close" onclick="closeChatLogModal()">&times;</span>
2064
- <h2 id="chatLogModalTitle"></h2>
2065
- <div id="chatLogModalBody"></div>
2066
  </div>
2067
  </div>
2068
 
@@ -2109,33 +2220,6 @@ ADMIN_TEMPLATE = '''
2109
  console.warn("Could not find parent .color-input-group for remove button");
2110
  }
2111
  }
2112
-
2113
- const chats = {{ chats|tojson }};
2114
-
2115
- function openChatLogModal(sessionId) {
2116
- const modal = document.getElementById('chatLogModal');
2117
- const title = document.getElementById('chatLogModalTitle');
2118
- const body = document.getElementById('chatLogModalBody');
2119
-
2120
- title.innerText = `Диалог: ${sessionId}`;
2121
- body.innerHTML = '';
2122
-
2123
- const messages = chats[sessionId];
2124
- if (messages) {
2125
- messages.forEach(msg => {
2126
- const msgDiv = document.createElement('div');
2127
- msgDiv.className = `chat-log-message ${msg.role}`;
2128
- msgDiv.innerText = `${msg.text} (${msg.timestamp})`;
2129
- body.appendChild(msgDiv);
2130
- });
2131
- }
2132
-
2133
- modal.style.display = 'block';
2134
- }
2135
-
2136
- function closeChatLogModal() {
2137
- document.getElementById('chatLogModal').style.display = 'none';
2138
- }
2139
 
2140
  async function generateDescription(photoInputId, descriptionTextareaId, languageSelectId) {
2141
  const photoInput = document.getElementById(photoInputId);
@@ -2201,6 +2285,45 @@ ADMIN_TEMPLATE = '''
2201
  };
2202
  reader.readAsDataURL(file);
2203
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2204
  </script>
2205
  </body>
2206
  </html>
@@ -2314,7 +2437,6 @@ def create_order():
2314
  except Exception as e:
2315
  return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
2316
 
2317
-
2318
  @app.route('/order/<order_id>')
2319
  def view_order(order_id):
2320
  data = load_data()
@@ -2338,9 +2460,6 @@ def admin():
2338
  if 'orders' not in data or not isinstance(data.get('orders'), dict):
2339
  data['orders'] = {}
2340
 
2341
- if 'chats' not in data or not isinstance(data.get('chats'), dict):
2342
- data['chats'] = {}
2343
-
2344
  if request.method == 'POST':
2345
  action = request.form.get('action')
2346
 
@@ -2609,21 +2728,6 @@ def admin():
2609
  repo_id=REPO_ID,
2610
  currency_code=CURRENCY_CODE
2611
  )
2612
-
2613
- @app.route('/chat')
2614
- def chat():
2615
- data = load_data()
2616
- all_products_raw = data.get('products', [])
2617
- products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2618
- products_sorted_for_js = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
2619
-
2620
- return render_template_string(
2621
- CHAT_TEMPLATE,
2622
- products_json=json.dumps(products_sorted_for_js),
2623
- repo_id=REPO_ID,
2624
- currency_code=CURRENCY_CODE,
2625
- uuid4=uuid4
2626
- )
2627
 
2628
  @app.route('/generate_description_ai', methods=['POST'])
2629
  def handle_generate_description_ai():
@@ -2648,20 +2752,54 @@ def handle_chat_with_ai():
2648
  request_data = request.get_json()
2649
  user_message = request_data.get('message')
2650
  chat_history_from_client = request_data.get('history', [])
2651
- session_id = request_data.get('session_id')
2652
 
2653
  if not user_message:
2654
  return jsonify({"error": "Сообщение не может быть пустым."}), 400
2655
-
2656
- if not session_id:
2657
- return jsonify({"error": "ID сессии отсутствует."}), 400
2658
 
2659
  try:
2660
- ai_response_text = generate_chat_response(user_message, chat_history_from_client, session_id)
 
 
 
 
 
 
 
 
 
 
 
 
2661
  return jsonify({"text": ai_response_text})
2662
  except Exception as e:
2663
  return jsonify({"error": f"Ошибка чата: {e}"}), 500
2664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2665
  @app.route('/force_upload', methods=['POST'])
2666
  def force_upload():
2667
  try:
@@ -2695,4 +2833,4 @@ if __name__ == '__main__':
2695
  pass
2696
 
2697
  port = int(os.environ.get('PORT', 7860))
2698
- app.run(debug=False, host='0.0.0.0', port=port)
 
32
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
33
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
34
 
35
+ STORE_ADDRESS = "Рынок Кербент, 6 ряд , 43 контейнер "
36
  WHATSAPP_NUMBER = "+996701202013"
37
 
38
  CURRENCY_CODE = 'KGS'
 
247
  final_prompt = f"{base_prompt}{lang_suffix}"
248
 
249
  try:
250
+ model = genai.GenerativeModel('gemma-3-27b-it')
251
 
252
  response = model.generate_content([final_prompt, image])
253
 
 
266
  elif " Billing account not found" in str(e):
267
  raise ValueError("Проблема с биллингом аккаунта Google Cloud. Проверьте ваш аккаунт.")
268
  elif "Could not find model" in str(e):
269
+ raise ValueError(f"Модель 'learnlm-2.0-flash-experimental' не найдена или недоступна.")
270
  elif "resource has been exhausted" in str(e).lower():
271
  raise ValueError("Квота запросов исчерпана. Попробуйте позже.")
272
  elif "content has been blocked" in str(e).lower():
 
277
  else:
278
  raise ValueError(f"Ошибка при генерации контента: {e}")
279
 
280
+ def generate_chat_response(message, chat_history_from_client):
281
  if not configure_gemini():
282
  return "Извините, сервис чата временно недоступен. Пожалуйста, попробуйте позже."
283
 
 
329
  response = None
330
 
331
  try:
332
+ model = genai.GenerativeModel('gemma-3-27b-it')
333
 
334
  model_chat_history_for_gemini = [
335
  {'role': 'user', 'parts': [{'text': system_instruction_content}]}
336
  ]
337
+ for entry in chat_history_from_client:
 
 
 
 
 
338
  gemini_role = 'model' if entry['role'] == 'ai' else 'user'
339
  model_chat_history_for_gemini.append({
340
  'role': gemini_role,
 
355
  generated_text = response.text
356
  else:
357
  raise ValueError("AI did not return a valid text response.")
 
 
 
 
 
358
 
359
  return generated_text
360
 
 
373
  else:
374
  return f"Извините, произошла ошибка: {e}"
375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  CATALOG_TEMPLATE = '''
377
  <!DOCTYPE html>
378
  <html lang="ru">
 
556
  justify-content: center;
557
  box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4);
558
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
559
+ text-decoration: none;
560
  }
561
  .floating-button:hover {
562
  background-color: var(--accent-hover);
 
605
  .formulate-order-button:hover { background-color: var(--accent-hover); }
606
  .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;}
607
  .notification.show { opacity: 1;}
 
 
 
 
 
 
 
 
 
 
 
 
608
  .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; }
609
  .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
610
  .chat-product-card-info { flex-grow: 1; }
 
615
  .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
616
  .chat-product-card-actions .fa-cart-plus { font-size: 0.9em; }
617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  </style>
619
  </head>
620
  <body>
 
659
  {% endif %}
660
  </div>
661
  <div class="product-info-overlay">
662
+ <span class="product-price">{{ "%.0f"|format(product.price) }} {{ currency_code }}</span>
663
  </div>
664
  </div>
665
  {% endfor %}
 
714
  </div>
715
 
716
  <div class="floating-buttons-container">
717
+ <a href="/chat" id="chat-open-button" class="floating-button" aria-label="Открыть чат">
718
  <i class="fas fa-comment-dots"></i>
719
  </a>
720
  <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
 
732
  const currencyCode = '{{ currency_code }}';
733
  let selectedProductId = null;
734
  let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
735
+
736
  function getProductById(productId) {
737
  return allProducts.find(p => p.product_id === productId);
738
  }
 
883
  cartButton.style.display = 'flex';
884
  } else {
885
  cartCountElement.style.display = 'none';
 
886
  }
887
  }
888
 
 
919
  </div>
920
  `;
921
  }).join('');
922
+ cartTotalElement.textContent = total.toFixed(2);
923
+ }
924
+ const modal = document.getElementById('cartModal');
925
+ if (modal) {
926
+ modal.style.display = "block";
927
+ document.body.style.overflow = 'hidden';
928
+ }
929
+ }
930
+
931
+ function incrementCartItem(itemId) {
932
+ const itemIndex = cart.findIndex(item => item.id === itemId);
933
+ if (itemIndex > -1) {
934
+ cart[itemIndex].quantity++;
935
+ localStorage.setItem('mekaCart', JSON.stringify(cart));
936
+ openCartModal();
937
+ updateCartButton();
938
+ }
939
+ }
940
+
941
+ function decrementCartItem(itemId) {
942
+ const itemIndex = cart.findIndex(item => item.id === itemId);
943
+ if (itemIndex > -1) {
944
+ cart[itemIndex].quantity--;
945
+ if (cart[itemIndex].quantity <= 0) {
946
+ cart.splice(itemIndex, 1);
947
+ }
948
+ localStorage.setItem('mekaCart', JSON.stringify(cart));
949
+ openCartModal();
950
+ updateCartButton();
951
+ }
952
+ }
953
+
954
+ function removeFromCart(itemId) {
955
+ cart = cart.filter(item => item.id !== itemId);
956
+ localStorage.setItem('mekaCart', JSON.stringify(cart));
957
+ openCartModal();
958
+ updateCartButton();
959
+ }
960
+
961
+ function clearCart() {
962
+ if (confirm("Вы уверены, что хотите очистить корзину?")) {
963
+ cart = [];
964
+ localStorage.removeItem('mekaCart');
965
+ openCartModal();
966
+ updateCartButton();
967
+ }
968
+ }
969
+
970
+ function formulateOrder() {
971
+ if (cart.length === 0) {
972
+ alert("Корзина пуста! Добавьте товары перед формированием заказа.");
973
+ return;
974
+ }
975
+ const orderData = { cart: cart };
976
+ const formulateButton = document.querySelector('.formulate-order-button');
977
+ if (formulateButton) formulateButton.disabled = true;
978
+ showNotification("Формируем заказ...", 5000);
979
+ fetch('/create_order', {
980
+ method: 'POST',
981
+ headers: { 'Content-Type': 'application/json' },
982
+ body: JSON.stringify(orderData)
983
+ })
984
+ .then(response => {
985
+ if (!response.ok) {
986
+ return response.json().then(err => { throw new Error(err.error || 'Не удалось создать заказ'); });
987
+ }
988
+ return response.json();
989
+ })
990
+ .then(data => {
991
+ if (data.order_id) {
992
+ localStorage.removeItem('mekaCart');
993
+ cart = [];
994
+ updateCartButton();
995
+ closeModal('cartModal');
996
+ window.location.href = `/order/${data.order_id}`;
997
+ } else {
998
+ throw new Error('Не получен ID заказа от сервера.');
999
+ }
1000
+ })
1001
+ .catch(error => {
1002
+ console.error('Ошибка при формировании заказа:', error);
1003
+ alert(`Ошибка: ${error.message}`);
1004
+ if (formulateButton) formulateButton.disabled = false;
1005
+ });
1006
+ }
1007
+
1008
+ function filterProducts() {
1009
+ const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
1010
+ const allCategorySections = document.querySelectorAll('.category-section');
1011
+ const noResultsEl = document.getElementById('no-results-message');
1012
+ let totalResults = 0;
1013
+
1014
+ allCategorySections.forEach(section => {
1015
+ const productCards = section.querySelectorAll('.product-card');
1016
+ let categoryHasVisibleProducts = false;
1017
+ productCards.forEach(card => {
1018
+ const name = card.dataset.name || '';
1019
+ const description = card.dataset.description || '';
1020
+ if (searchTerm === '' || name.includes(searchTerm) || description.includes(searchTerm)) {
1021
+ card.style.display = 'inline-block';
1022
+ categoryHasVisibleProducts = true;
1023
+ } else {
1024
+ card.style.display = 'none';
1025
+ }
1026
+ });
1027
+ if (categoryHasVisibleProducts) {
1028
+ section.style.display = 'block';
1029
+ totalResults++;
1030
+ } else {
1031
+ section.style.display = 'none';
1032
+ }
1033
+ });
1034
+
1035
+ if (totalResults === 0 && searchTerm !== '') {
1036
+ if (noResultsEl) noResultsEl.style.display = 'block';
1037
+ } else {
1038
+ if (noResultsEl) noResultsEl.style.display = 'none';
1039
+ }
1040
+ }
1041
+
1042
+ function showNotification(message, duration = 3000) {
1043
+ const placeholder = document.getElementById('notification-placeholder');
1044
+ if (!placeholder) return;
1045
+ const notification = document.createElement('div');
1046
+ notification.className = 'notification';
1047
+ notification.textContent = message;
1048
+ placeholder.appendChild(notification);
1049
+ void notification.offsetWidth;
1050
+ notification.classList.add('show');
1051
+ setTimeout(() => {
1052
+ notification.classList.remove('show');
1053
+ notification.addEventListener('transitionend', () => notification.remove());
1054
+ }, duration);
1055
+ }
1056
+
1057
+ document.addEventListener('DOMContentLoaded', () => {
1058
+ updateCartButton();
1059
+ document.getElementById('search-input').addEventListener('input', filterProducts);
1060
+ window.addEventListener('click', function(event) {
1061
+ if (event.target.classList.contains('modal')) { closeModal(event.target.id); }
1062
+ });
1063
+ window.addEventListener('keydown', function(event) {
1064
+ if (event.key === 'Escape') {
1065
+ document.querySelectorAll('.modal[style*="display: block"]').forEach(modal => {
1066
+ closeModal(modal.id);
1067
+ });
1068
+ }
1069
+ });
1070
+ });
1071
+ </script>
1072
+ </body>
1073
+ </html>
1074
+ '''
1075
+
1076
+ CHAT_TEMPLATE = '''
1077
+ <!DOCTYPE html>
1078
+ <html lang="ru">
1079
+ <head>
1080
+ <meta charset="UTF-8">
1081
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
1082
+ <title>Gippo312 - Чат с EVA</title>
1083
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1084
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
1085
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1086
+ <style>
1087
+ :root {
1088
+ --bg-dark: #003C43;
1089
+ --bg-medium: #135D66;
1090
+ --accent: #48D1CC;
1091
+ --accent-hover: #77E4D8;
1092
+ --text-light: #E3FEF7;
1093
+ --text-dark: #333;
1094
+ --danger: #E57373;
1095
+ --danger-hover: #EF5350;
1096
+ --chat-bg: #f0f2f5;
1097
+ }
1098
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1099
+ html { -webkit-tap-highlight-color: transparent; height: 100%; }
1100
+ body {
1101
+ font-family: 'Montserrat', sans-serif;
1102
+ background-color: var(--chat-bg);
1103
+ color: var(--text-dark);
1104
+ display: flex;
1105
+ flex-direction: column;
1106
+ height: 100%;
1107
+ overflow: hidden;
1108
+ }
1109
+ .chat-container {
1110
+ display: flex;
1111
+ flex-direction: column;
1112
+ height: 100%;
1113
+ width: 100%;
1114
+ max-width: 800px;
1115
+ margin: 0 auto;
1116
+ background: #fff;
1117
+ box-shadow: 0 0 20px rgba(0,0,0,0.05);
1118
+ }
1119
+ .chat-header {
1120
+ display: flex;
1121
+ align-items: center;
1122
+ padding: 10px 15px;
1123
+ background: var(--bg-dark);
1124
+ color: var(--text-light);
1125
+ flex-shrink: 0;
1126
+ }
1127
+ .chat-header a { color: var(--text-light); font-size: 1.2rem; text-decoration: none; }
1128
+ .chat-header .logo { width: 40px; height: 40px; border-radius: 50%; margin: 0 15px; border: 2px solid var(--accent); }
1129
+ .chat-header h1 { font-size: 1.2rem; font-weight: 600; }
1130
+ #chat-messages {
1131
+ flex-grow: 1;
1132
+ overflow-y: auto;
1133
+ padding: 20px 15px;
1134
+ display: flex;
1135
+ flex-direction: column;
1136
+ gap: 12px;
1137
+ }
1138
+ .chat-message {
1139
+ display: flex;
1140
+ flex-direction: column;
1141
+ max-width: 85%;
1142
+ animation: message-appear 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
1143
+ }
1144
+ @keyframes message-appear {
1145
+ from { opacity: 0; transform: translateY(15px); }
1146
+ to { opacity: 1; transform: translateY(0); }
1147
+ }
1148
+ .message-bubble {
1149
+ padding: 10px 15px;
1150
+ border-radius: 18px;
1151
+ line-height: 1.5;
1152
+ word-wrap: break-word;
1153
+ }
1154
+ .chat-message.user { align-self: flex-end; }
1155
+ .chat-message.user .message-bubble {
1156
+ background-color: var(--bg-medium);
1157
+ color: white;
1158
+ border-bottom-right-radius: 4px;
1159
+ }
1160
+ .chat-message.ai { align-self: flex-start; }
1161
+ .chat-message.ai .message-bubble {
1162
+ background-color: #e6e6e6;
1163
+ color: var(--text-dark);
1164
+ border-bottom-left-radius: 4px;
1165
+ }
1166
+ .chat-input-container {
1167
+ padding: 15px;
1168
+ background: #fff;
1169
+ border-top: 1px solid #ddd;
1170
+ display: flex;
1171
+ gap: 10px;
1172
+ align-items: center;
1173
+ flex-shrink: 0;
1174
+ }
1175
+ #chat-input {
1176
+ flex-grow: 1;
1177
+ padding: 12px 18px;
1178
+ border: 1px solid #e0e0e0;
1179
+ border-radius: 24px;
1180
+ font-size: 1rem;
1181
+ outline: none;
1182
+ transition: border-color 0.3s, box-shadow 0.3s;
1183
+ }
1184
+ #chat-input:focus {
1185
+ border-color: var(--bg-medium);
1186
+ box-shadow: 0 0 0 3px rgba(19, 93, 102, 0.15);
1187
+ }
1188
+ #chat-send-button {
1189
+ background-color: var(--bg-medium);
1190
+ color: white;
1191
+ border: none;
1192
+ border-radius: 50%;
1193
+ width: 48px;
1194
+ height: 48px;
1195
+ display: flex;
1196
+ justify-content: center;
1197
+ align-items: center;
1198
+ cursor: pointer;
1199
+ transition: background-color 0.3s, transform 0.2s;
1200
+ flex-shrink: 0;
1201
+ font-size: 1.2rem;
1202
+ }
1203
+ #chat-send-button:hover { background-color: var(--bg-dark); }
1204
+ #chat-send-button:active { transform: scale(0.9); }
1205
+ #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
1206
+ .floating-buttons-container { position: fixed; bottom: 25px; right: 25px; z-index: 1000; }
1207
+ .floating-button { background-color: var(--accent); color: var(--bg-dark); border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
1208
+ .floating-button:hover { background-color: var(--accent-hover); transform: translateY(-3px); }
1209
+ #cart-button { position: relative; }
1210
+ #cart-count { position: absolute; top: -2px; right: -2px; background-color: var(--danger); color: white; border-radius: 50%; padding: 2px 6px; font-size: 0.7rem; font-weight: bold; border: 2px solid var(--accent); }
1211
+ .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(5px); overflow-y: auto; }
1212
+ .modal-content { background: #ffffff; color: var(--text-dark); margin: 5% auto; padding: 25px; border-radius: 15px; width: 90%; max-width: 700px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); animation: slideIn 0.3s ease-out; position: relative; }
1213
+ @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
1214
+ .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
1215
+ .close:hover { color: #666; }
1216
+ .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
1217
+ .cart-item { display: grid; grid-template-columns: 60px 1fr auto auto auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #e0e0e0; }
1218
+ .cart-item:last-child { border-bottom: none; }
1219
+ .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
1220
+ .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
1221
+ .cart-item-quantity { display: flex; align-items: center; gap: 8px; }
1222
+ .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; }
1223
+ .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--bg-medium);}
1224
+ .cart-item-remove { background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; }
1225
+ .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; }
1226
+ .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
1227
+ .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; }
1228
+ .product-button { display: block; width: auto; flex-grow: 1; padding: 10px; border: none; border-radius: 8px; color: white; cursor: pointer; text-align: center; text-decoration: none; }
1229
+ .clear-cart { background-color: #6c757d; }
1230
+ .formulate-order-button { background-color: var(--accent); color: var(--bg-dark); }
1231
+ .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;}
1232
+ .notification.show { opacity: 1;}
1233
+ .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; }
1234
+ .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
1235
+ .chat-product-card-info { flex-grow: 1; }
1236
+ .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
1237
+ .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
1238
+ .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
1239
+ .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%; }
1240
+ .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
1241
+ </style>
1242
+ </head>
1243
+ <body>
1244
+ <div class="chat-container">
1245
+ <div class="chat-header">
1246
+ <a href="/"><i class="fas fa-arrow-left"></i></a>
1247
+ <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Logo" class="logo">
1248
+ <h1>Чат с EVA</h1>
1249
+ </div>
1250
+ <div id="chat-messages"></div>
1251
+ <div class="chat-input-container">
1252
+ <input type="text" id="chat-input" placeholder="Напишите сообщение..." autocomplete="off">
1253
+ <button id="chat-send-button"><i class="fas fa-paper-plane"></i></button>
1254
+ </div>
1255
+ </div>
1256
+
1257
+ <div class="floating-buttons-container">
1258
+ <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
1259
+ <i class="fas fa-shopping-cart"></i>
1260
+ <span id="cart-count">0</span>
1261
+ </button>
1262
+ </div>
1263
+
1264
+ <div id="productModal" class="modal">
1265
+ <div class="modal-content">
1266
+ <span class="close" onclick="closeModal('productModal')">×</span>
1267
+ <div id="modalContent"></div>
1268
+ </div>
1269
+ </div>
1270
+ <div id="quantityModal" class="modal">
1271
+ <div class="modal-content">
1272
+ <span class="close" onclick="closeModal('quantityModal')">×</span>
1273
+ <h2>Укажите количество и цвет</h2>
1274
+ <input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
1275
+ <select id="colorSelect" class="color-select"></select>
1276
+ <button class="product-button formulate-order-button" onclick="confirmAddToCart()">Добавить в корзину</button>
1277
+ </div>
1278
+ </div>
1279
+ <div id="cartModal" class="modal">
1280
+ <div class="modal-content">
1281
+ <span class="close" onclick="closeModal('cartModal')">×</span>
1282
+ <h2><i class="fas fa-shopping-cart"></i> Ваша корзина</h2>
1283
+ <div id="cartContent"></div>
1284
+ <div class="cart-summary">
1285
+ <strong>Итого: <span id="cartTotal">0.00</span> {{ currency_code }}</strong>
1286
+ </div>
1287
+ <div class="cart-actions">
1288
+ <button class="product-button clear-cart" onclick="clearCart()">Очистить</button>
1289
+ <button class="product-button formulate-order-button" onclick="formulateOrder()">Сформировать</button>
1290
+ </div>
1291
+ </div>
1292
+ </div>
1293
+ <div id="notification-placeholder"></div>
1294
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.js"></script>
1295
+ <script>
1296
+ const allProducts = {{ products_json|safe }};
1297
+ const repoId = '{{ repo_id }}';
1298
+ const currencyCode = '{{ currency_code }}';
1299
+ let selectedProductId = null;
1300
+ let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
1301
+ let chatHistory = [];
1302
+ let chatId = sessionStorage.getItem('gippoChatId');
1303
+ if (!chatId) {
1304
+ chatId = 'chat_' + new Date().getTime() + '_' + Math.random().toString(36).substr(2, 9);
1305
+ sessionStorage.setItem('gippoChatId', chatId);
1306
+ }
1307
+
1308
+ const chatMessagesDiv = document.getElementById('chat-messages');
1309
+ const chatInput = document.getElementById('chat-input');
1310
+ const chatSendButton = document.getElementById('chat-send-button');
1311
+
1312
+ function getProductById(productId) { return allProducts.find(p => p.product_id === productId); }
1313
+ function getProductIndexById(productId) { return allProducts.findIndex(p => p.product_id === productId); }
1314
+ function openModalById(productId) {
1315
+ const productIndex = getProductIndexById(productId);
1316
+ if (productIndex === -1) return;
1317
+ loadProductDetails(productIndex);
1318
+ document.getElementById('productModal').style.display = "block";
1319
+ document.body.style.overflow = 'hidden';
1320
+ }
1321
+ function closeModal(modalId) {
1322
+ document.getElementById(modalId).style.display = "none";
1323
+ if (!document.querySelector('.modal[style*="display: block"]')) {
1324
+ document.body.style.overflow = 'auto';
1325
+ }
1326
+ }
1327
+ function loadProductDetails(index) {
1328
+ const modalContent = document.getElementById('modalContent');
1329
+ modalContent.innerHTML = '<p>Загрузка...</p>';
1330
+ fetch('/product/' + index)
1331
+ .then(response => response.text())
1332
+ .then(data => {
1333
+ modalContent.innerHTML = data;
1334
+ initializeSwiper();
1335
+ }).catch(error => modalContent.innerHTML = `<p>Не удалось загрузить информацию о товаре.</p>`);
1336
+ }
1337
+ function initializeSwiper() {
1338
+ if (document.querySelector('#productModal .swiper-container')) {
1339
+ new Swiper('#productModal .swiper-container', { loop: true, pagination: { el: '.swiper-pagination' }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' } });
1340
+ }
1341
+ }
1342
+ function openQuantityModalById(productId) {
1343
+ selectedProductId = productId;
1344
+ const product = getProductById(productId);
1345
+ if (!product) return;
1346
+ const colorSelect = document.getElementById('colorSelect');
1347
+ colorSelect.innerHTML = '';
1348
+ const validColors = product.colors ? product.colors.filter(c => c && c.trim()) : [];
1349
+ if (validColors.length > 0) {
1350
+ validColors.forEach(color => {
1351
+ const option = document.createElement('option');
1352
+ option.value = option.text = color.trim();
1353
+ colorSelect.appendChild(option);
1354
+ });
1355
+ colorSelect.style.display = 'block';
1356
+ } else {
1357
+ colorSelect.style.display = 'none';
1358
+ }
1359
+ document.getElementById('quantityInput').value = 1;
1360
+ document.getElementById('quantityModal').style.display = "block";
1361
+ }
1362
+ function confirmAddToCart() {
1363
+ const quantity = parseInt(document.getElementById('quantityInput').value);
1364
+ const colorSelect = document.getElementById('colorSelect');
1365
+ const color = colorSelect.style.display !== 'none' ? colorSelect.value : 'N/A';
1366
+ if (isNaN(quantity) || quantity <= 0) {
1367
+ alert("Укажите корректное количество.");
1368
+ return;
1369
+ }
1370
+ const product = getProductById(selectedProductId);
1371
+ const cartItemId = `${product.product_id}-${color}`;
1372
+ const existingItem = cart.find(item => item.id === cartItemId);
1373
+ if (existingItem) {
1374
+ existingItem.quantity += quantity;
1375
+ } else {
1376
+ cart.push({ id: cartItemId, product_id: product.product_id, name: product.name, price: product.price, photo: product.photos ? product.photos[0] : null, quantity, color });
1377
+ }
1378
+ localStorage.setItem('mekaCart', JSON.stringify(cart));
1379
+ closeModal('quantityModal');
1380
+ updateCartButton();
1381
+ showNotification(`${product.name} добавлен в корзину!`);
1382
+ }
1383
+ function updateCartButton() {
1384
+ const cartCountEl = document.getElementById('cart-count');
1385
+ const cartButton = document.getElementById('cart-button');
1386
+ const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
1387
+ if (totalItems > 0) {
1388
+ cartCountEl.textContent = totalItems;
1389
+ cartCountEl.style.display = 'flex';
1390
+ cartButton.style.display = 'flex';
1391
+ } else {
1392
+ cartCountEl.style.display = 'none';
1393
+ cartButton.style.display = 'none';
1394
+ }
1395
+ }
1396
+ function openCartModal() {
1397
+ const cartContent = document.getElementById('cartContent');
1398
+ const cartTotalEl = document.getElementById('cartTotal');
1399
+ let total = 0;
1400
+ if (cart.length === 0) {
1401
+ cartContent.innerHTML = '<p>Ваша корзина пуста.</p>';
1402
+ cartTotalEl.textContent = '0.00';
1403
+ } else {
1404
+ cartContent.innerHTML = cart.map(item => {
1405
+ const itemTotal = item.price * item.quantity;
1406
+ total += itemTotal;
1407
+ return `<div class="cart-item">
1408
+ <img src="${item.photo ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}` : ''}" alt="${item.name}">
1409
+ <div><strong>${item.name} ${item.color !== 'N/A' ? `(${item.color})` : ''}</strong><p>${item.price.toFixed(2)} ${currencyCode}</p></div>
1410
+ <div class="cart-item-quantity"><button class="quantity-btn" onclick="decrementCartItem('${item.id}')">-</button><span>${item.quantity}</span><button class="quantity-btn" onclick="incrementCartItem('${item.id}')">+</button></div>
1411
+ <span class="cart-item-total">${itemTotal.toFixed(2)}</span>
1412
+ <button class="cart-item-remove" onclick="removeFromCart('${item.id}')"><i class="fas fa-trash-alt"></i></button>
1413
+ </div>`;
1414
+ }).join('');
1415
+ cartTotalEl.textContent = total.toFixed(2);
1416
  }
1417
+ document.getElementById('cartModal').style.display = 'block';
 
 
 
 
1418
  }
 
1419
  function incrementCartItem(itemId) {
1420
+ const item = cart.find(i => i.id === itemId);
1421
+ if (item) item.quantity++;
1422
+ localStorage.setItem('mekaCart', JSON.stringify(cart));
1423
+ openCartModal();
1424
+ updateCartButton();
 
 
1425
  }
 
1426
  function decrementCartItem(itemId) {
1427
+ const itemIndex = cart.findIndex(i => i.id === itemId);
1428
  if (itemIndex > -1) {
1429
  cart[itemIndex].quantity--;
1430
+ if (cart[itemIndex].quantity <= 0) cart.splice(itemIndex, 1);
 
 
1431
  localStorage.setItem('mekaCart', JSON.stringify(cart));
1432
  openCartModal();
1433
  updateCartButton();
1434
  }
1435
  }
 
1436
  function removeFromCart(itemId) {
1437
  cart = cart.filter(item => item.id !== itemId);
1438
  localStorage.setItem('mekaCart', JSON.stringify(cart));
1439
  openCartModal();
1440
  updateCartButton();
1441
  }
 
1442
  function clearCart() {
1443
+ if (confirm("Очистить корзину?")) {
1444
  cart = [];
1445
  localStorage.removeItem('mekaCart');
1446
  openCartModal();
1447
  updateCartButton();
1448
  }
1449
  }
 
1450
  function formulateOrder() {
1451
+ if (cart.length === 0) return;
1452
+ fetch('/create_order', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cart }) })
1453
+ .then(res => res.json())
1454
+ .then(data => {
1455
+ if (data.order_id) {
1456
+ localStorage.removeItem('mekaCart');
1457
+ cart = [];
1458
+ updateCartButton();
1459
+ closeModal('cartModal');
1460
+ window.location.href = `/order/${data.order_id}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1461
  }
1462
  });
1463
+ }
1464
+ function showNotification(message, duration = 3000) {
1465
+ const el = document.createElement('div');
1466
+ el.className = 'notification';
1467
+ el.textContent = message;
1468
+ document.getElementById('notification-placeholder').appendChild(el);
1469
+ setTimeout(() => {
1470
+ el.classList.add('show');
1471
+ setTimeout(() => {
1472
+ el.classList.remove('show');
1473
+ el.addEventListener('transitionend', () => el.remove());
1474
+ }, duration);
1475
+ }, 10);
1476
+ }
1477
+
1478
+ function addMessageToChatUI(text, role) {
1479
+ const messageElement = document.createElement('div');
1480
+ messageElement.className = `chat-message ${role}`;
1481
+
1482
+ const productRegex = /\[ID_ТОВАРА:\\s*([a-fA-F0-9]+)\\s*Название:\\s*([^\]]+)\]/g;
1483
+ let lastIndex = 0;
1484
+ const contentFragment = document.createDocumentFragment();
1485
+ let match;
1486
+
1487
+ while ((match = productRegex.exec(text)) !== null) {
1488
+ if (match.index > lastIndex) {
1489
+ const textNode = document.createElement('div');
1490
+ textNode.className = 'message-bubble';
1491
+ textNode.innerHTML = text.substring(lastIndex, match.index).replace(/\\n/g, '<br>');
1492
+ messageElement.appendChild(textNode);
1493
  }
1494
+ const productId = match[1];
1495
+ const product = getProductById(productId);
1496
+ if (product) {
1497
+ const photoUrl = product.photos && product.photos.length > 0 ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${product.photos[0]}` : '';
1498
+ const card = document.createElement('div');
1499
+ card.className = 'chat-product-card';
1500
+ card.innerHTML = `<img src="${photoUrl}" alt="${product.name}">
1501
+ <div class="chat-product-card-info">
1502
+ <strong>${product.name}</strong>
1503
+ <span>${product.price.toFixed(0)} ${currencyCode}</span>
1504
+ </div>
1505
+ <div class="chat-product-card-actions">
1506
+ <a href="#" class="chat-product-link" data-id="${productId}">Обзор</a>
1507
+ <a href="#" class="chat-add-to-cart" data-id="${productId}"><i class="fas fa-cart-plus"></i></a>
1508
+ </div>`;
1509
+ messageElement.appendChild(card);
1510
+ }
1511
+ lastIndex = match.index + match[0].length;
1512
+ }
1513
 
1514
+ if (lastIndex < text.length) {
1515
+ const textNode = document.createElement('div');
1516
+ textNode.className = 'message-bubble';
1517
+ textNode.innerHTML = text.substring(lastIndex).replace(/\\n/g, '<br>');
1518
+ messageElement.appendChild(textNode);
1519
  }
1520
+
1521
+ chatMessagesDiv.appendChild(messageElement);
1522
+ chatMessagesDiv.scrollTop = chatMessagesDiv.scrollHeight;
1523
  }
1524
 
1525
+ async function sendMessage() {
1526
+ const message = chatInput.value.trim();
1527
+ if (!message) return;
1528
+ addMessageToChatUI(message, 'user');
1529
+ chatHistory.push({ role: 'user', text: message });
1530
+ chatInput.value = '';
1531
+ chatSendButton.disabled = true;
 
 
 
 
 
 
 
1532
 
1533
+ try {
1534
+ const response = await fetch('/chat_with_ai', {
1535
+ method: 'POST',
1536
+ headers: { 'Content-Type': 'application/json' },
1537
+ body: JSON.stringify({ message: message, history: chatHistory.slice(0, -1), chat_id: chatId })
1538
+ });
1539
+ const result = await response.json();
1540
+ if (!response.ok) throw new Error(result.error);
1541
+ addMessageToChatUI(result.text, 'ai');
1542
+ chatHistory.push({ role: 'ai', text: result.text });
1543
+ } catch (error) {
1544
+ addMessageToChatUI(`Ошибка: ${error.message}`, 'ai');
1545
+ } finally {
1546
+ chatSendButton.disabled = false;
1547
+ chatInput.focus();
1548
+ }
1549
+ }
1550
+
1551
+ chatSendButton.addEventListener('click', sendMessage);
1552
+ chatInput.addEventListener('keypress', e => { if (e.key === 'Enter') sendMessage(); });
1553
+
1554
+ chatMessagesDiv.addEventListener('click', e => {
1555
+ if(e.target.closest('.chat-product-link')) {
1556
+ e.preventDefault();
1557
+ openModalById(e.target.closest('.chat-product-link').dataset.id);
1558
+ }
1559
+ if(e.target.closest('.chat-add-to-cart')) {
1560
+ e.preventDefault();
1561
+ openQuantityModalById(e.target.closest('.chat-add-to-cart').dataset.id);
1562
+ }
1563
+ });
1564
+
1565
  document.addEventListener('DOMContentLoaded', () => {
1566
  updateCartButton();
1567
+ window.addEventListener('click', e => { if (e.target.classList.contains('modal')) closeModal(e.target.id); });
1568
+ window.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal').forEach(m => closeModal(m.id)); });
1569
+
1570
+ addMessageToChatUI('👋 Здравствуйте! Я ваш виртуальный помощник EVA. Чем могу помочь?', 'ai');
1571
+ chatHistory.push({ role: 'ai', text: 'Здравствуйте! Я ваш виртуальный помощник EVA. Чем могу помочь?' });
 
 
 
 
 
 
1572
  });
1573
  </script>
1574
  </body>
 
1604
  </div>
1605
 
1606
  <div style="text-align:center; margin-top:20px; padding: 0 10px;">
1607
+ <p style="font-size: 1.5rem; font-weight: bold; color: #135D66; margin-bottom: 15px;"><strong>Цена:</strong> {{ "%.0f"|format(product.price) }} {{ currency_code }}</p>
1608
  <button class="product-button formulate-order-button" style="padding: 12px 30px; width: 100%; max-width: 300px;" onclick="closeModal('productModal'); openQuantityModalById('{{ product.get('product_id', '') }}')">
1609
  <i class="fas fa-cart-plus"></i> В корзину
1610
  </button>
 
1666
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--bg-medium); text-decoration: none; font-size: 0.9rem; }
1667
  .catalog-link:hover { text-decoration: underline; }
1668
  .not-found { text-align: center; color: #dc3545; font-size: 1.2rem; padding: 40px 0;}
 
 
 
 
 
 
 
 
1669
  </style>
1670
  </head>
1671
  <body>
 
1871
  .status-indicator.top-product { background-color: #FFF9C4; color: #F57F17; margin-left: 5px;}
1872
  .ai-generate-button { background-color: #8D6EC8; color: white; margin-top: 5px; margin-bottom: 10px; }
1873
  .ai-generate-button:hover { background-color: #7B4DB5; }
1874
+ .chat-log-item { padding: 10px; border: 1px solid #eee; border-radius: 5px; cursor: pointer; transition: background-color 0.2s; }
1875
+ .chat-log-item:hover { background-color: #f7f7f7; }
1876
+ .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); }
1877
+ .modal-content { background: #fff; margin: 10% auto; padding: 20px; border-radius: 8px; width: 90%; max-width: 600px; }
1878
+ .chat-message-viewer { max-height: 60vh; overflow-y: auto; }
1879
+ .chat-message.user { text-align: right; margin: 5px 0; }
1880
+ .chat-message.ai { text-align: left; margin: 5px 0; }
1881
+ .chat-message .bubble { display: inline-block; padding: 8px 12px; border-radius: 15px; max-width: 80%; }
1882
+ .chat-message.user .bubble { background-color: #dcf8c6; }
1883
+ .chat-message.ai .bubble { background-color: #f1f1f1; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1884
  </style>
1885
  </head>
1886
  <body>
 
1900
  {% endfor %}
1901
  {% endif %}
1902
  {% endwith %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1903
 
1904
  <div class="section">
1905
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
 
1914
  <p style="font-size: 0.85rem; color: #999;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
1915
  </div>
1916
 
1917
+ <div class="section">
1918
+ <h2><i class="fas fa-comments"></i> Диалоги с EVA</h2>
1919
+ <div class="item-list">
1920
+ {% if chats %}
1921
+ {% for chat_id, chat_data in chats.items()|sort(reverse=True) %}
1922
+ <div class="chat-log-item" onclick="viewChat('{{ chat_id }}')">
1923
+ <strong>ID Диалога:</strong> {{ chat_id }}
1924
+ <br>
1925
+ <small>Сообщений: {{ chat_data|length }} | Последнее сообщение: {{ chat_data[-1].timestamp if chat_data and 'timestamp' in chat_data[-1] else 'N/A' }}</small>
1926
+ </div>
1927
+ {% endfor %}
1928
+ {% else %}
1929
+ <p>Пока не было ни одного диалога.</p>
1930
+ {% endif %}
1931
+ </div>
1932
+ </div>
1933
+
1934
  <div class="flex-container">
1935
  <div class="flex-item">
1936
  <div class="section">
 
2168
  <p>Товаров пока нет.</p>
2169
  {% endif %}
2170
  </div>
 
2171
  </div>
2172
+ <div id="chat-modal" class="modal">
2173
+ <div class="modal-content">
2174
+ <span class="button delete-button" style="float:right;" onclick="closeModal('chat-modal')">&times;</span>
2175
+ <h2 id="chat-modal-title">Просмотр диалога</h2>
2176
+ <div id="chat-modal-body" class="chat-message-viewer"></div>
 
2177
  </div>
2178
  </div>
2179
 
 
2220
  console.warn("Could not find parent .color-input-group for remove button");
2221
  }
2222
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2223
 
2224
  async function generateDescription(photoInputId, descriptionTextareaId, languageSelectId) {
2225
  const photoInput = document.getElementById(photoInputId);
 
2285
  };
2286
  reader.readAsDataURL(file);
2287
  }
2288
+
2289
+ function openModal(modalId) {
2290
+ document.getElementById(modalId).style.display = 'block';
2291
+ }
2292
+ function closeModal(modalId) {
2293
+ document.getElementById(modalId).style.display = 'none';
2294
+ }
2295
+
2296
+ async function viewChat(chatId) {
2297
+ const modalBody = document.getElementById('chat-modal-body');
2298
+ modalBody.innerHTML = 'Загрузка...';
2299
+ openModal('chat-modal');
2300
+ try {
2301
+ const response = await fetch(`/get_chat/${chatId}`);
2302
+ if(!response.ok) throw new Error('Chat not found');
2303
+ const chatHistory = await response.json();
2304
+
2305
+ modalBody.innerHTML = '';
2306
+ chatHistory.forEach(msg => {
2307
+ const msgDiv = document.createElement('div');
2308
+ msgDiv.className = `chat-message ${msg.role}`;
2309
+
2310
+ const bubble = document.createElement('div');
2311
+ bubble.className = 'bubble';
2312
+ bubble.innerText = msg.text;
2313
+
2314
+ msgDiv.appendChild(bubble);
2315
+ modalBody.appendChild(msgDiv);
2316
+ });
2317
+
2318
+ } catch (e) {
2319
+ modalBody.innerHTML = 'Не удалось загрузить диалог.';
2320
+ }
2321
+ }
2322
+ window.onclick = function(event) {
2323
+ if (event.target.classList.contains('modal')) {
2324
+ closeModal(event.target.id);
2325
+ }
2326
+ }
2327
  </script>
2328
  </body>
2329
  </html>
 
2437
  except Exception as e:
2438
  return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500
2439
 
 
2440
  @app.route('/order/<order_id>')
2441
  def view_order(order_id):
2442
  data = load_data()
 
2460
  if 'orders' not in data or not isinstance(data.get('orders'), dict):
2461
  data['orders'] = {}
2462
 
 
 
 
2463
  if request.method == 'POST':
2464
  action = request.form.get('action')
2465
 
 
2728
  repo_id=REPO_ID,
2729
  currency_code=CURRENCY_CODE
2730
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2731
 
2732
  @app.route('/generate_description_ai', methods=['POST'])
2733
  def handle_generate_description_ai():
 
2752
  request_data = request.get_json()
2753
  user_message = request_data.get('message')
2754
  chat_history_from_client = request_data.get('history', [])
2755
+ chat_id = request_data.get('chat_id')
2756
 
2757
  if not user_message:
2758
  return jsonify({"error": "Сообщение не может быть пустым."}), 400
2759
+ if not chat_id:
2760
+ return jsonify({"error": "ID чата не предоставлен."}), 400
 
2761
 
2762
  try:
2763
+ ai_response_text = generate_chat_response(user_message, chat_history_from_client)
2764
+
2765
+ data = load_data()
2766
+ if 'chats' not in data:
2767
+ data['chats'] = {}
2768
+ if chat_id not in data['chats']:
2769
+ data['chats'][chat_id] = []
2770
+
2771
+ timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
2772
+ data['chats'][chat_id].append({'role': 'user', 'text': user_message, 'timestamp': timestamp})
2773
+ data['chats'][chat_id].append({'role': 'ai', 'text': ai_response_text, 'timestamp': timestamp})
2774
+ save_data(data)
2775
+
2776
  return jsonify({"text": ai_response_text})
2777
  except Exception as e:
2778
  return jsonify({"error": f"Ошибка чата: {e}"}), 500
2779
 
2780
+ @app.route('/chat')
2781
+ def chat_page():
2782
+ data = load_data()
2783
+ all_products_raw = data.get('products', [])
2784
+ products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2785
+ products_sorted_for_js = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
2786
+
2787
+ return render_template_string(
2788
+ CHAT_TEMPLATE,
2789
+ products_json=json.dumps(products_sorted_for_js),
2790
+ repo_id=REPO_ID,
2791
+ currency_code=CURRENCY_CODE
2792
+ )
2793
+
2794
+ @app.route('/get_chat/<chat_id>')
2795
+ def get_chat_history(chat_id):
2796
+ data = load_data()
2797
+ chat_history = data.get('chats', {}).get(chat_id)
2798
+ if chat_history:
2799
+ return jsonify(chat_history)
2800
+ else:
2801
+ return jsonify({"error": "Chat not found"}), 404
2802
+
2803
  @app.route('/force_upload', methods=['POST'])
2804
  def force_upload():
2805
  try:
 
2833
  pass
2834
 
2835
  port = int(os.environ.get('PORT', 7860))
2836
+ app.run(debug=False, host='0.0.0.0', port=port)```