Kgshop commited on
Commit
8ca375b
·
verified ·
1 Parent(s): 7e7da4d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +234 -180
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'
@@ -76,7 +76,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN
76
  try:
77
  if file_name == DATA_FILE:
78
  with open(file_name, 'w', encoding='utf-8') as f:
79
- json.dump({'products': [], 'categories': [], 'orders': {}, 'organization_info': {}, 'chat_sessions': {}}, f)
80
  except Exception as create_e:
81
  pass
82
  success = False
@@ -134,7 +134,7 @@ def load_data():
134
  "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
135
  "contact": f"Наш магазин находится по адресу: {STORE_ADDRESS}. Связаться с нами можно по телефону: {WHATSAPP_NUMBER} или через WhatsApp по этому же номеру. Мы работаем ежедневно с 9:00 до 18:00."
136
  }
137
- default_data = {'products': [], 'categories': [], 'orders': {}, 'organization_info': default_organization_info, 'chat_sessions': {}}
138
  data = default_data
139
  try:
140
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
@@ -145,7 +145,7 @@ def load_data():
145
  if 'categories' not in data: data['categories'] = []
146
  if 'orders' not in data: data['orders'] = {}
147
  if 'organization_info' not in data: data['organization_info'] = default_organization_info
148
- if 'chat_sessions' not in data: data['chat_sessions'] = {}
149
  except FileNotFoundError:
150
  if download_db_from_hf(specific_file=DATA_FILE):
151
  try:
@@ -157,7 +157,7 @@ def load_data():
157
  if 'categories' not in data: data['categories'] = []
158
  if 'orders' not in data: data['orders'] = {}
159
  if 'organization_info' not in data: data['organization_info'] = default_organization_info
160
- if 'chat_sessions' not in data: data['chat_sessions'] = {}
161
  except (FileNotFoundError, json.JSONDecodeError, Exception) as e:
162
  data = default_data
163
  else:
@@ -173,7 +173,7 @@ def load_data():
173
  if 'categories' not in data: data['categories'] = []
174
  if 'orders' not in data: data['orders'] = {}
175
  if 'organization_info' not in data: data['organization_info'] = default_organization_info
176
- if 'chat_sessions' not in data: data['chat_sessions'] = {}
177
  except (FileNotFoundError, json.JSONDecodeError, Exception) as e:
178
  data = default_data
179
  else:
@@ -203,7 +203,7 @@ def save_data(data):
203
  if 'categories' not in data: data['categories'] = []
204
  if 'orders' not in data: data['orders'] = {}
205
  if 'organization_info' not in data: data['organization_info'] = {}
206
- if 'chat_sessions' not in data: data['chat_sessions'] = {}
207
 
208
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
209
  json.dump(data, file, ensure_ascii=False, indent=4)
@@ -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('gemini-1.5-flash')
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"Модель 'gemini-1.5-flash' не найдена или недоступна.")
270
  elif "resource has been exhausted" in str(e).lower():
271
  raise ValueError("Квота запросов исчерпана. Попробуйте позже.")
272
  elif "content has been blocked" in str(e).lower():
@@ -329,12 +329,17 @@ def generate_chat_response(message, chat_history_from_client, session_id):
329
  response = None
330
 
331
  try:
332
- model = genai.GenerativeModel('gemini-1.5-flash')
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,14 +360,10 @@ def generate_chat_response(message, chat_history_from_client, session_id):
355
  generated_text = response.text
356
  else:
357
  raise ValueError("AI did not return a valid text response.")
358
-
359
- if 'chat_sessions' not in data:
360
- data['chat_sessions'] = {}
361
- if session_id not in data['chat_sessions']:
362
- data['chat_sessions'][session_id] = {'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'history': []}
363
-
364
- data['chat_sessions'][session_id]['history'].append({'role': 'user', 'text': message, 'timestamp': datetime.now().isoformat()})
365
- data['chat_sessions'][session_id]['history'].append({'role': 'ai', 'text': generated_text, 'timestamp': datetime.now().isoformat()})
366
  save_data(data)
367
 
368
  return generated_text
@@ -381,6 +382,7 @@ def generate_chat_response(message, chat_history_from_client, session_id):
381
  return f"Извините, Ваш запрос был заблокирован из-за политики безопасности (причина: {reason}). Пожалуйста, переформулируйте его."
382
  else:
383
  return f"Извините, произошла ошибка: {e}"
 
384
  CHAT_TEMPLATE = '''
385
  <!DOCTYPE html>
386
  <html lang="ru">
@@ -401,7 +403,7 @@ CHAT_TEMPLATE = '''
401
  --danger: #E57373;
402
  }
403
  * { margin: 0; padding: 0; box-sizing: border-box; }
404
- html, body { height: 100%; overflow: hidden; }
405
  body {
406
  font-family: 'Montserrat', sans-serif;
407
  background-color: var(--bg-dark);
@@ -433,38 +435,34 @@ CHAT_TEMPLATE = '''
433
  margin-right: 15px;
434
  border: 2px solid var(--accent);
435
  }
436
- .chat-header h1 { font-size: 1.2rem; font-weight: 600; }
437
- .chat-header a {
 
 
 
438
  margin-left: auto;
439
  color: var(--accent);
440
- text-decoration: none;
441
  font-size: 1.5rem;
 
442
  }
443
- #chat-messages {
444
  flex-grow: 1;
445
  overflow-y: auto;
446
  padding: 20px;
447
  display: flex;
448
  flex-direction: column;
449
  gap: 12px;
 
450
  }
451
  .chat-message {
452
- padding: 10px 15px;
453
- border-radius: 18px;
454
  max-width: 85%;
455
  word-wrap: break-word;
456
  line-height: 1.5;
457
- animation: fadeIn 0.4s ease-out;
458
- opacity: 0;
459
- transform: translateY(10px);
460
- animation-fill-mode: forwards;
461
- }
462
- @keyframes fadeIn {
463
- to {
464
- opacity: 1;
465
- transform: translateY(0);
466
- }
467
  }
 
468
  .chat-message.user {
469
  align-self: flex-end;
470
  background-color: var(--bg-medium);
@@ -473,80 +471,55 @@ CHAT_TEMPLATE = '''
473
  }
474
  .chat-message.ai {
475
  align-self: flex-start;
476
- background-color: #E3FEF7;
477
- color: var(--text-dark);
478
  border-bottom-left-radius: 4px;
479
  }
480
  .chat-input-container {
481
  display: flex;
482
  gap: 10px;
483
- padding: 15px 20px;
484
  background-color: var(--bg-dark);
485
  border-top: 1px solid var(--bg-medium);
486
  flex-shrink: 0;
487
  }
488
  #chat-input {
489
  flex-grow: 1;
490
- padding: 12px 18px;
491
- border: 1px solid var(--bg-medium);
492
  border-radius: 25px;
493
  font-size: 1rem;
494
  outline: none;
495
  background-color: var(--bg-medium);
496
  color: var(--text-light);
497
- transition: border-color 0.3s;
498
  }
499
- #chat-input:focus { border-color: var(--accent); }
500
  #chat-send-button {
501
  background-color: var(--accent);
502
  color: var(--bg-dark);
503
  border: none;
504
  border-radius: 50%;
505
- width: 50px;
506
- height: 50px;
507
  display: flex;
508
  justify-content: center;
509
  align-items: center;
510
  cursor: pointer;
511
- transition: all 0.3s;
512
  flex-shrink: 0;
513
  font-size: 1.2rem;
514
  }
515
- #chat-send-button:hover { background-color: var(--accent-hover); transform: scale(1.1); }
516
  #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
517
- .chat-product-card {
518
- background-color: #ffffff;
519
- border-radius: 12px;
520
- padding: 12px;
521
- margin-top: 10px;
522
- display: grid;
523
- grid-template-columns: 60px 1fr;
524
- gap: 12px;
525
- border: 1px solid #e0e0e0;
526
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
527
- animation: slideInCard 0.5s ease-out;
528
- }
529
- @keyframes slideInCard { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
530
- .chat-product-card img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
531
- .chat-product-card-info { grid-column: 2; }
532
  .chat-product-card-info strong { display: block; font-size: 1rem; color: var(--text-dark); margin-bottom: 4px; }
533
- .chat-product-card-info span { font-size: 0.9rem; color: var(--bg-medium); font-weight: 600; }
534
- .chat-product-card-actions { grid-column: 1 / -1; display: flex; gap: 10px; margin-top: 10px; }
535
- .chat-product-link, .chat-add-to-cart {
536
- flex: 1;
537
- display: inline-block;
538
- background-color: #E0F2F1;
539
- color: var(--bg-medium);
540
- padding: 8px 12px;
541
- border-radius: 20px;
542
- cursor: pointer;
543
- font-size: 0.9rem;
544
- text-decoration: none;
545
- transition: all 0.2s;
546
- font-weight: 500;
547
- text-align: center;
548
- }
549
- .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
550
  </style>
551
  </head>
552
  <body>
@@ -554,9 +527,9 @@ CHAT_TEMPLATE = '''
554
  <div class="chat-header">
555
  <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Gippo312 Logo">
556
  <h1>Чат с EVA</h1>
557
- <a href="{{ url_for('catalog') }}" title="Вернуться в каталог"><i class="fas fa-store"></i></a>
558
  </div>
559
- <div id="chat-messages"></div>
560
  <div class="chat-input-container">
561
  <input type="text" id="chat-input" placeholder="Напишите сообщение...">
562
  <button id="chat-send-button"><i class="fas fa-paper-plane"></i></button>
@@ -569,7 +542,7 @@ CHAT_TEMPLATE = '''
569
  let chatHistory = JSON.parse(localStorage.getItem('evaChatHistory') || '[]');
570
  let sessionId = localStorage.getItem('chatSessionId');
571
  if (!sessionId) {
572
- sessionId = `session_${new Date().getTime()}_${Math.random().toString(36).substr(2, 9)}`;
573
  localStorage.setItem('chatSessionId', sessionId);
574
  }
575
 
@@ -580,16 +553,14 @@ CHAT_TEMPLATE = '''
580
  function displayChatHistory() {
581
  const chatMessagesDiv = document.getElementById('chat-messages');
582
  chatMessagesDiv.innerHTML = '';
583
- chatHistory.forEach(msg => {
584
- addMessageToChat(msg.text, msg.role, false);
585
- });
586
  }
587
 
588
  function addMessageToChat(text, role, save = true) {
589
  const chatMessagesDiv = document.getElementById('chat-messages');
590
  const messageElement = document.createElement('div');
591
  messageElement.className = `chat-message ${role}`;
592
-
593
  const productMatchRegex = /\[ID_ТОВАРА:\s*([a-fA-F0-9]+)\s*Название:\s*([^\]]+)\]/g;
594
  let lastIndex = 0;
595
  const contentFragment = document.createDocumentFragment();
@@ -619,11 +590,15 @@ CHAT_TEMPLATE = '''
619
  <span>${product.price.toFixed(2)} ${currencyCode}</span>
620
  </div>
621
  <div class="chat-product-card-actions">
622
- <a href="#" class="chat-product-link" data-product-id="${productId}">Обзор</a>
623
- <a href="#" class="chat-add-to-cart" data-product-id="${productId}"><i class="fas fa-cart-plus"></i> В корзину</a>
624
  </div>
625
  `;
626
  contentFragment.appendChild(card);
 
 
 
 
 
627
  }
628
  lastIndex = match.index + match[0].length;
629
  }
@@ -643,31 +618,22 @@ CHAT_TEMPLATE = '''
643
  localStorage.setItem('evaChatHistory', JSON.stringify(chatHistory));
644
  }
645
 
646
- messageElement.querySelectorAll('.chat-product-link').forEach(link => {
647
- link.addEventListener('click', (e) => {
648
- e.preventDefault();
649
- const id = e.currentTarget.dataset.productId;
650
- alert('Функция просмотра деталей товара из этого чата будет реализована.');
651
- });
652
- });
653
-
654
  messageElement.querySelectorAll('.chat-add-to-cart').forEach(link => {
655
  link.addEventListener('click', (e) => {
656
  e.preventDefault();
657
- const productId = e.currentTarget.dataset.productId;
658
- addToCartFromChat(productId);
 
 
 
659
  });
660
  });
661
  }
662
 
663
- function addToCartFromChat(productId) {
664
- const product = getProductById(productId);
665
- if (!product) return;
666
-
667
  let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
668
- const cartItemId = `${product.product_id}-N/A`; // Simplified: no color selection from chat
669
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
670
-
671
  if (existingItemIndex > -1) {
672
  cart[existingItemIndex].quantity += 1;
673
  } else {
@@ -693,12 +659,11 @@ CHAT_TEMPLATE = '''
693
  addMessageToChat(message, 'user');
694
  chatInput.value = '';
695
  chatSendButton.disabled = true;
696
-
697
  try {
698
  const response = await fetch('/chat_with_ai', {
699
  method: 'POST',
700
  headers: { 'Content-Type': 'application/json' },
701
- body: JSON.stringify({ message: message, history: chatHistory.slice(-10), session_id: sessionId })
702
  });
703
  const result = await response.json();
704
  if (!response.ok) {
@@ -706,10 +671,10 @@ CHAT_TEMPLATE = '''
706
  }
707
  addMessageToChat(result.text, 'ai');
708
  } catch (error) {
 
709
  addMessageToChat(`Извините, произошла ошибка: ${error.message}`, 'ai', false);
710
  } finally {
711
  chatSendButton.disabled = false;
712
- chatInput.focus();
713
  }
714
  }
715
 
@@ -719,14 +684,12 @@ CHAT_TEMPLATE = '''
719
  document.getElementById('chat-input').addEventListener('keypress', function(e) {
720
  if (e.key === 'Enter') { sendMessage(); }
721
  });
722
- if(chatHistory.length === 0){
723
- addMessageToChat("Здравствуйте! Я EVA, ваш виртуальный помощник. Чем могу помочь?", 'ai');
724
- }
725
  });
726
  </script>
727
  </body>
728
  </html>
729
  '''
 
730
  CATALOG_TEMPLATE = '''
731
  <!DOCTYPE html>
732
  <html lang="ru">
@@ -910,7 +873,6 @@ CATALOG_TEMPLATE = '''
910
  justify-content: center;
911
  box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4);
912
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
913
- text-decoration: none;
914
  }
915
  .floating-button:hover {
916
  background-color: var(--accent-hover);
@@ -959,6 +921,54 @@ CATALOG_TEMPLATE = '''
959
  .formulate-order-button:hover { background-color: var(--accent-hover); }
960
  .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;}
961
  .notification.show { opacity: 1;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
962
  </style>
963
  </head>
964
  <body>
@@ -1076,7 +1086,7 @@ CATALOG_TEMPLATE = '''
1076
  const currencyCode = '{{ currency_code }}';
1077
  let selectedProductId = null;
1078
  let cart = JSON.parse(localStorage.getItem('mekaCart') || '[]');
1079
-
1080
  function getProductById(productId) {
1081
  return allProducts.find(p => p.product_id === productId);
1082
  }
@@ -1509,6 +1519,14 @@ ORDER_TEMPLATE = '''
1509
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--bg-medium); text-decoration: none; font-size: 0.9rem; }
1510
  .catalog-link:hover { text-decoration: underline; }
1511
  .not-found { text-align: center; color: #dc3545; font-size: 1.2rem; padding: 40px 0;}
 
 
 
 
 
 
 
 
1512
  </style>
1513
  </head>
1514
  <body>
@@ -1714,14 +1732,44 @@ ADMIN_TEMPLATE = '''
1714
  .status-indicator.top-product { background-color: #FFF9C4; color: #F57F17; margin-left: 5px;}
1715
  .ai-generate-button { background-color: #8D6EC8; color: white; margin-top: 5px; margin-bottom: 10px; }
1716
  .ai-generate-button:hover { background-color: #7B4DB5; }
1717
- .dialog-list .item { cursor: pointer; transition: background-color 0.2s; }
1718
- .dialog-list .item:hover { background-color: #f0f8ff; }
1719
- .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); overflow-y: auto; }
1720
- .modal-content { background: #fff; margin: 10% auto; padding: 20px; border-radius: 8px; width: 90%; max-width: 600px; }
1721
- .chat-history { max-height: 400px; overflow-y: auto; }
1722
- .chat-message { margin-bottom: 10px; padding: 8px 12px; border-radius: 10px; }
1723
- .chat-message.user { background-color: #e1f5fe; text-align: right; }
1724
- .chat-message.ai { background-color: #f1f8e9; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1725
  </style>
1726
  </head>
1727
  <body>
@@ -1741,6 +1789,21 @@ ADMIN_TEMPLATE = '''
1741
  {% endfor %}
1742
  {% endif %}
1743
  {% endwith %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1744
 
1745
  <div class="section">
1746
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
@@ -1754,23 +1817,6 @@ ADMIN_TEMPLATE = '''
1754
  </div>
1755
  <p style="font-size: 0.85rem; color: #999;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
1756
  </div>
1757
-
1758
- <div class="section">
1759
- <h2><i class="fas fa-comments"></i> Диалоги с EVA</h2>
1760
- <div class="item-list dialog-list">
1761
- {% if chat_sessions %}
1762
- {% for session_id, session_data in chat_sessions|dictsort(by='value.created_at', reverse=True) %}
1763
- <div class="item" onclick="openDialogModal('{{ session_id }}')">
1764
- <p><strong>ID сессии:</strong> {{ session_id }}</p>
1765
- <p><strong>Дата:</strong> {{ session_data.created_at }}</p>
1766
- <p><strong>Сообщений:</strong> {{ session_data.history|length }}</p>
1767
- </div>
1768
- {% endfor %}
1769
- {% else %}
1770
- <p>Сохраненных диалогов пока нет.</p>
1771
- {% endif %}
1772
- </div>
1773
- </div>
1774
 
1775
  <div class="flex-container">
1776
  <div class="flex-item">
@@ -2009,13 +2055,14 @@ ADMIN_TEMPLATE = '''
2009
  <p>Товаров пока нет.</p>
2010
  {% endif %}
2011
  </div>
 
2012
  </div>
2013
 
2014
- <div id="dialogModal" class="modal">
2015
- <div class="modal-content">
2016
- <span class="close" onclick="closeDialogModal()">&times;</span>
2017
- <h2 id="dialogModalTitle">История диалога</h2>
2018
- <div class="chat-history" id="dialogModalBody"></div>
2019
  </div>
2020
  </div>
2021
 
@@ -2063,31 +2110,31 @@ ADMIN_TEMPLATE = '''
2063
  }
2064
  }
2065
 
2066
- const chatSessionsData = {{ chat_sessions|tojson|safe }};
2067
-
2068
- function openDialogModal(sessionId) {
2069
- const session = chatSessionsData[sessionId];
2070
- if (!session) return;
 
2071
 
2072
- const modal = document.getElementById('dialogModal');
2073
- const title = document.getElementById('dialogModalTitle');
2074
- const body = document.getElementById('dialogModalBody');
2075
-
2076
- title.textContent = `Диалог от ${session.created_at}`;
2077
  body.innerHTML = '';
2078
 
2079
- session.history.forEach(msg => {
2080
- const msgDiv = document.createElement('div');
2081
- msgDiv.className = `chat-message ${msg.role}`;
2082
- msgDiv.textContent = msg.text;
2083
- body.appendChild(msgDiv);
2084
- });
2085
-
 
 
 
2086
  modal.style.display = 'block';
2087
  }
2088
 
2089
- function closeDialogModal() {
2090
- document.getElementById('dialogModal').style.display = 'none';
2091
  }
2092
 
2093
  async function generateDescription(photoInputId, descriptionTextareaId, languageSelectId) {
@@ -2286,10 +2333,13 @@ def admin():
2286
  products = data.get('products', [])
2287
  categories = data.get('categories', [])
2288
  organization_info = data.get('organization_info', {})
2289
- chat_sessions = data.get('chat_sessions', {})
2290
 
2291
  if 'orders' not in data or not isinstance(data.get('orders'), dict):
2292
  data['orders'] = {}
 
 
 
2293
 
2294
  if request.method == 'POST':
2295
  action = request.form.get('action')
@@ -2548,17 +2598,32 @@ def admin():
2548
  display_products = sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower())
2549
  display_categories = sorted(current_data.get('categories', []))
2550
  display_organization_info = current_data.get('organization_info', {})
2551
- display_chat_sessions = current_data.get('chat_sessions', {})
2552
 
2553
  return render_template_string(
2554
  ADMIN_TEMPLATE,
2555
  products=display_products,
2556
  categories=display_categories,
2557
  organization_info=display_organization_info,
2558
- chat_sessions=display_chat_sessions,
2559
  repo_id=REPO_ID,
2560
  currency_code=CURRENCY_CODE
2561
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2562
 
2563
  @app.route('/generate_description_ai', methods=['POST'])
2564
  def handle_generate_description_ai():
@@ -2585,8 +2650,11 @@ def handle_chat_with_ai():
2585
  chat_history_from_client = request_data.get('history', [])
2586
  session_id = request_data.get('session_id')
2587
 
2588
- if not user_message or not session_id:
2589
- return jsonify({"error": "Сообщение и ID сессии обязательны."}), 400
 
 
 
2590
 
2591
  try:
2592
  ai_response_text = generate_chat_response(user_message, chat_history_from_client, session_id)
@@ -2615,20 +2683,6 @@ def force_download():
2615
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
2616
  return redirect(url_for('admin'))
2617
 
2618
- @app.route('/chat')
2619
- def chat():
2620
- data = load_data()
2621
- all_products_raw = data.get('products', [])
2622
- products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2623
- products_sorted_for_js = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
2624
-
2625
- return render_template_string(
2626
- CHAT_TEMPLATE,
2627
- products_json=json.dumps(products_sorted_for_js),
2628
- repo_id=REPO_ID,
2629
- currency_code=CURRENCY_CODE
2630
- )
2631
-
2632
  if __name__ == '__main__':
2633
  configure_gemini()
2634
  download_db_from_hf()
 
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'
 
76
  try:
77
  if file_name == DATA_FILE:
78
  with open(file_name, 'w', encoding='utf-8') as f:
79
+ json.dump({'products': [], 'categories': [], 'orders': {}, 'organization_info': {}, 'chats': {}}, f)
80
  except Exception as create_e:
81
  pass
82
  success = False
 
134
  "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
135
  "contact": f"Наш магазин находится по адресу: {STORE_ADDRESS}. Связаться с нами можно по телефону: {WHATSAPP_NUMBER} или через WhatsApp по этому же номеру. Мы работаем ежедневно с 9:00 до 18:00."
136
  }
137
+ default_data = {'products': [], 'categories': [], 'orders': {}, 'organization_info': default_organization_info, 'chats': {}}
138
  data = default_data
139
  try:
140
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
 
145
  if 'categories' not in data: data['categories'] = []
146
  if 'orders' not in data: data['orders'] = {}
147
  if 'organization_info' not in data: data['organization_info'] = default_organization_info
148
+ if 'chats' not in data: data['chats'] = {}
149
  except FileNotFoundError:
150
  if download_db_from_hf(specific_file=DATA_FILE):
151
  try:
 
157
  if 'categories' not in data: data['categories'] = []
158
  if 'orders' not in data: data['orders'] = {}
159
  if 'organization_info' not in data: data['organization_info'] = default_organization_info
160
+ if 'chats' not in data: data['chats'] = {}
161
  except (FileNotFoundError, json.JSONDecodeError, Exception) as e:
162
  data = default_data
163
  else:
 
173
  if 'categories' not in data: data['categories'] = []
174
  if 'orders' not in data: data['orders'] = {}
175
  if 'organization_info' not in data: data['organization_info'] = default_organization_info
176
+ if 'chats' not in data: data['chats'] = {}
177
  except (FileNotFoundError, json.JSONDecodeError, Exception) as e:
178
  data = default_data
179
  else:
 
203
  if 'categories' not in data: data['categories'] = []
204
  if 'orders' not in data: data['orders'] = {}
205
  if 'organization_info' not in data: data['organization_info'] = {}
206
+ if 'chats' not in data: data['chats'] = {}
207
 
208
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
209
  json.dump(data, file, ensure_ascii=False, indent=4)
 
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
  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():
 
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
  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
 
382
  return f"Извините, Ваш запрос был заблокирован из-за политики безопасности (причина: {reason}). Пожалуйста, переформулируйте его."
383
  else:
384
  return f"Извините, произошла ошибка: {e}"
385
+
386
  CHAT_TEMPLATE = '''
387
  <!DOCTYPE html>
388
  <html lang="ru">
 
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);
 
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);
 
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>
 
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>
 
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
 
 
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();
 
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
  }
 
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 {
 
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) {
 
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
 
 
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
  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
  .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; }
939
+ .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
940
+ .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
941
+ .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
942
+ .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%; }
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>
 
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
  }
 
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
  .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
  {% 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>
 
1817
  </div>
1818
  <p style="font-size: 0.85rem; color: #999;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
1819
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1820
 
1821
  <div class="flex-container">
1822
  <div class="flex-item">
 
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
 
 
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) {
 
2333
  products = data.get('products', [])
2334
  categories = data.get('categories', [])
2335
  organization_info = data.get('organization_info', {})
2336
+ chats = data.get('chats', {})
2337
 
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')
 
2598
  display_products = sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower())
2599
  display_categories = sorted(current_data.get('categories', []))
2600
  display_organization_info = current_data.get('organization_info', {})
2601
+ display_chats = current_data.get('chats', {})
2602
 
2603
  return render_template_string(
2604
  ADMIN_TEMPLATE,
2605
  products=display_products,
2606
  categories=display_categories,
2607
  organization_info=display_organization_info,
2608
+ chats=display_chats,
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():
 
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)
 
2683
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
2684
  return redirect(url_for('admin'))
2685
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2686
  if __name__ == '__main__':
2687
  configure_gemini()
2688
  download_db_from_hf()