Kgshop commited on
Commit
9a91292
·
verified ·
1 Parent(s): 127da28

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +260 -94
app.py CHANGED
@@ -6,7 +6,7 @@ import json
6
  import logging
7
  import threading
8
  import time
9
- from datetime import datetime
10
  from uuid import uuid4
11
  import random
12
  import string
@@ -37,6 +37,8 @@ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
37
  DOWNLOAD_RETRIES = 3
38
  DOWNLOAD_DELAY = 5
39
 
 
 
40
  CURRENCIES = {
41
  'KGS': 'Кыргызский сом',
42
  'KZT': 'Казахстанский тенге',
@@ -50,7 +52,10 @@ COLOR_SCHEMES = {
50
  'default': 'Бирюзовый (по умолч.)',
51
  'forest': 'Лесной зеленый',
52
  'ocean': 'Глубокий синий',
53
- 'sunset': 'Закатный оранжевый'
 
 
 
54
  }
55
 
56
 
@@ -167,20 +172,43 @@ def save_data(data):
167
  except Exception as e:
168
  pass
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  def get_env_data(env_id):
171
  all_data = load_data()
172
  default_organization_info = {
173
- "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров. Мы предлагаем широкий ассортимент продукции, от электроники до товаров для дома, всегда стремясь к качеству и доступности. Наша миссия — сделать ваш шопинг приятным и удобным, предлагая только лучшие товары, тщательно отобранные для вас.",
174
  "shipping": "Доставка осуществляется по всему Кыргызстану. Стоимость и сроки доставки зависят от региона и веса товара. По Бишкеку доставка возможна в течение 1-2 рабочих дней, в регионы — от 3 до 7 дней. Для уточнения деталей свяжитесь с нами.",
175
  "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
176
  "contact": f"Наш магазин находится по адресу: Рынок Кербен, 6 ряд , 43 контейнер. Связаться с нами можно по телефону или через WhatsApp. Мы работаем ежедневно с 9:00 до 18:00."
177
  }
178
  default_settings = {
 
179
  "whatsapp_number": "+996701202013",
180
  "currency_code": "KGS",
181
  "chat_name": "EVA",
182
  "chat_avatar": None,
183
- "color_scheme": "default"
 
 
184
  }
185
 
186
  env_data = all_data.get(env_id, {})
@@ -288,6 +316,9 @@ def generate_ai_description_from_image(image_data, language):
288
  raise ValueError(f"Ошибка при генерации контента: {e}")
289
 
290
  def generate_chat_response(message, chat_history_from_client, env_id):
 
 
 
291
  if not configure_gemini():
292
  return "Извините, сервис чата временно недоступен. Пожалуйста, попробуйте позже."
293
 
@@ -298,6 +329,7 @@ def generate_chat_response(message, chat_history_from_client, env_id):
298
  settings = data.get('settings', {})
299
  currency_code = settings.get('currency_code', 'KGS')
300
  chat_name = settings.get('chat_name', 'EVA')
 
301
 
302
  product_info_list = []
303
  for p in products:
@@ -322,7 +354,7 @@ def generate_chat_response(message, chat_history_from_client, env_id):
322
 
323
 
324
  system_instruction_content = (
325
- f"Ты - доброжелательный и очень полезный виртуальный консультант по имени {chat_name} для магазина Gippo312. "
326
  "Твоя задача - помогать пользователям находить товары, отвечать на вопросы о них, предлагать варианты, а также предоставлять информацию о магазине. "
327
  "Всегда будь вежлив, информативен и стремись решить проблему пользователя. "
328
  "Никогда не выдумывай товары или категории, которых нет в предоставленных списках. "
@@ -404,6 +436,8 @@ ADMHOSTO_TEMPLATE = '''
404
  --text-dark: #333;
405
  --text-on-accent: #003C43;
406
  --danger: #E57373;
 
 
407
  }
408
  body { font-family: 'Montserrat', sans-serif; background-color: var(--bg-light); color: var(--text-dark); padding: 20px; }
409
  .container { max-width: 900px; margin: 0 auto; background-color: #fff; padding: 25px; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.05); }
@@ -412,11 +446,16 @@ ADMHOSTO_TEMPLATE = '''
412
  .add-env-form { margin-bottom: 20px; text-align: center; }
413
  .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: var(--accent); color: var(--text-on-accent); font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; }
414
  .button:hover { background-color: var(--accent-hover); }
 
415
  .env-list { list-style: none; padding: 0; }
416
- .env-item { background: #fdfdff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;}
 
417
  .env-id { font-weight: 600; color: var(--bg-medium); font-size: 1.2rem; }
 
 
418
  .env-actions { display: flex; gap: 10px; flex-wrap: wrap; }
419
  .delete-button { background-color: var(--danger); color: white; }
 
420
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; text-align: center; }
421
  .message.success { background-color: #d4edda; color: #155724; }
422
  .message.error { background-color: #f8d7da; color: #721c24; }
@@ -444,13 +483,32 @@ ADMHOSTO_TEMPLATE = '''
444
  <h2><i class="fas fa-list-ul"></i> Существующие среды</h2>
445
  {% if environments %}
446
  <ul class="env-list">
447
- {% for env_id in environments %}
448
  <li class="env-item">
449
- <span class="env-id">{{ env_id }}</span>
 
 
 
 
 
 
 
 
 
450
  <div class="env-actions">
451
- <a href="{{ url_for('admin', env_id=env_id) }}" class="button" target="_blank"><i class="fas fa-tools"></i> Админ-панель</a>
452
- <a href="{{ url_for('catalog', env_id=env_id) }}" class="button" target="_blank"><i class="fas fa-store"></i> Каталог (Чат)</a>
453
- <form method="POST" action="{{ url_for('delete_environment', env_id=env_id) }}" style="display:inline;" onsubmit="return confirm('Вы уверены, что хотите удалить среду {{ env_id }}? Это действие необратимо.');">
 
 
 
 
 
 
 
 
 
 
454
  <button type="submit" class="button delete-button"><i class="fas fa-trash-alt"></i></button>
455
  </form>
456
  </div>
@@ -472,54 +530,38 @@ CATALOG_TEMPLATE = '''
472
  <head>
473
  <meta charset="UTF-8">
474
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
475
- <title>Gippo312 - Каталог</title>
476
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
477
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
478
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
479
  <style>
480
  {% if settings.color_scheme == 'forest' %}
481
  :root {
482
- --bg-dark: #2F4F4F;
483
- --bg-medium: #556B2F;
484
- --accent: #90EE90;
485
- --accent-hover: #98FB98;
486
- --text-light: #F5F5DC;
487
- --text-dark: #333;
488
- --danger: #CD5C5C;
489
- --danger-hover: #F08080;
490
  }
491
  {% elif settings.color_scheme == 'ocean' %}
492
  :root {
493
- --bg-dark: #000080;
494
- --bg-medium: #1E90FF;
495
- --accent: #87CEEB;
496
- --accent-hover: #ADD8E6;
497
- --text-light: #F0F8FF;
498
- --text-dark: #333;
499
- --danger: #FF6347;
500
- --danger-hover: #FF4500;
501
  }
502
  {% elif settings.color_scheme == 'sunset' %}
503
  :root {
504
- --bg-dark: #8B4513;
505
- --bg-medium: #D2691E;
506
- --accent: #FFA500;
507
- --accent-hover: #FFD700;
508
- --text-light: #FFF8DC;
509
- --text-dark: #333;
510
- --danger: #DC143C;
511
- --danger-hover: #FF0000;
 
 
 
 
 
512
  }
513
  {% else %}
514
  :root {
515
- --bg-dark: #003C43;
516
- --bg-medium: #135D66;
517
- --accent: #48D1CC;
518
- --accent-hover: #77E4D8;
519
- --text-light: #E3FEF7;
520
- --text-dark: #333;
521
- --danger: #E57373;
522
- --danger-hover: #EF5350;
523
  }
524
  {% endif %}
525
 
@@ -706,24 +748,30 @@ CATALOG_TEMPLATE = '''
706
  }
707
  .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; }
708
  .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; }
 
709
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
710
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
711
  .close:hover { color: #666; }
712
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
713
  .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; }
 
714
  .cart-item:last-child { border-bottom: none; }
715
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
716
  .cart-item-details { grid-column: 2; }
717
  .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
718
  .cart-item-price { font-size: 0.9rem; color: #666; }
 
719
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; grid-column: 3;}
720
  .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; font-size: 1.1rem; line-height: 1; display: flex; align-items: center; justify-content: center; }
 
721
  .cart-item-total { font-weight: bold; text-align: right; grid-column: 4; font-size: 1rem; color: var(--bg-medium);}
722
  .cart-item-remove { grid-column: 5; background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
723
  .cart-item-remove:hover { color: var(--danger-hover); }
724
  .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; box-sizing: border-box; }
 
725
  .quantity-input:focus, .color-select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px rgba(72, 209, 204, 0.2); }
726
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
 
727
  .cart-summary strong { font-size: 1.2rem; color: var(--bg-medium);}
728
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
729
  .product-button { display: block; width: auto; flex-grow: 1; padding: 10px; border: none; border-radius: 8px; color: white; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; text-align: center; text-decoration: none; }
@@ -745,7 +793,7 @@ CATALOG_TEMPLATE = '''
745
 
746
  </style>
747
  </head>
748
- <body>
749
  <div class="container">
750
  <div class="top-bar">
751
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="logo">
@@ -783,7 +831,7 @@ CATALOG_TEMPLATE = '''
783
  alt="{{ product.name }}"
784
  loading="lazy">
785
  {% else %}
786
- <img src="https://via.placeholder.com/170x170.png?text=Gippo312" alt="No Image" loading="lazy">
787
  {% endif %}
788
  </div>
789
  <div class="product-info-overlay">
@@ -842,9 +890,11 @@ CATALOG_TEMPLATE = '''
842
  </div>
843
 
844
  <div class="floating-buttons-container">
 
845
  <a href="{{ url_for('chat_page', env_id=env_id) }}" id="chat-open-button" class="floating-button" aria-label="Открыть чат">
846
  <i class="fas fa-comment-dots"></i>
847
  </a>
 
848
  <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
849
  <i class="fas fa-shopping-cart"></i>
850
  <span id="cart-count">0</span>
@@ -1208,58 +1258,38 @@ CHAT_TEMPLATE = '''
1208
  <head>
1209
  <meta charset="UTF-8">
1210
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
1211
- <title>Gippo312 - Чат с {{ settings.chat_name }}</title>
1212
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1213
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
1214
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1215
  <style>
1216
  {% if settings.color_scheme == 'forest' %}
1217
  :root {
1218
- --bg-dark: #2F4F4F;
1219
- --bg-medium: #556B2F;
1220
- --accent: #90EE90;
1221
- --accent-hover: #98FB98;
1222
- --text-light: #F5F5DC;
1223
- --text-dark: #333;
1224
- --danger: #CD5C5C;
1225
- --danger-hover: #F08080;
1226
- --chat-bg: #F5F5DC;
1227
  }
1228
  {% elif settings.color_scheme == 'ocean' %}
1229
  :root {
1230
- --bg-dark: #000080;
1231
- --bg-medium: #1E90FF;
1232
- --accent: #87CEEB;
1233
- --accent-hover: #ADD8E6;
1234
- --text-light: #F0F8FF;
1235
- --text-dark: #333;
1236
- --danger: #FF6347;
1237
- --danger-hover: #FF4500;
1238
- --chat-bg: #F0F8FF;
1239
  }
1240
  {% elif settings.color_scheme == 'sunset' %}
1241
  :root {
1242
- --bg-dark: #8B4513;
1243
- --bg-medium: #D2691E;
1244
- --accent: #FFA500;
1245
- --accent-hover: #FFD700;
1246
- --text-light: #FFF8DC;
1247
- --text-dark: #333;
1248
- --danger: #DC143C;
1249
- --danger-hover: #FF0000;
1250
- --chat-bg: #FFF8DC;
 
 
 
 
1251
  }
1252
  {% else %}
1253
  :root {
1254
- --bg-dark: #003C43;
1255
- --bg-medium: #135D66;
1256
- --accent: #48D1CC;
1257
- --accent-hover: #77E4D8;
1258
- --text-light: #E3FEF7;
1259
- --text-dark: #333;
1260
- --danger: #E57373;
1261
- --danger-hover: #EF5350;
1262
- --chat-bg: #f0f2f5;
1263
  }
1264
  {% endif %}
1265
 
@@ -1284,6 +1314,7 @@ CHAT_TEMPLATE = '''
1284
  background: #fff;
1285
  box-shadow: 0 0 20px rgba(0,0,0,0.05);
1286
  }
 
1287
  .chat-header {
1288
  display: flex;
1289
  align-items: center;
@@ -1331,6 +1362,7 @@ CHAT_TEMPLATE = '''
1331
  color: var(--text-dark);
1332
  border-bottom-left-radius: 4px;
1333
  }
 
1334
  .chat-input-container {
1335
  padding: 15px;
1336
  background: #fff;
@@ -1340,6 +1372,7 @@ CHAT_TEMPLATE = '''
1340
  align-items: center;
1341
  flex-shrink: 0;
1342
  }
 
1343
  #chat-input {
1344
  flex-grow: 1;
1345
  padding: 12px 18px;
@@ -1349,6 +1382,7 @@ CHAT_TEMPLATE = '''
1349
  outline: none;
1350
  transition: border-color 0.3s, box-shadow 0.3s;
1351
  }
 
1352
  #chat-input:focus {
1353
  border-color: var(--bg-medium);
1354
  box-shadow: 0 0 0 3px rgba(19, 93, 102, 0.15);
@@ -1378,20 +1412,26 @@ CHAT_TEMPLATE = '''
1378
  #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); }
1379
  .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; }
1380
  .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; }
 
1381
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
1382
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
1383
  .close:hover { color: #666; }
1384
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
1385
  .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; }
 
1386
  .cart-item:last-child { border-bottom: none; }
1387
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
1388
  .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
 
1389
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; }
1390
  .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; }
 
1391
  .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--bg-medium);}
1392
  .cart-item-remove { background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; }
1393
  .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; }
 
1394
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
 
1395
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; }
1396
  .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; }
1397
  .clear-cart { background-color: #6c757d; }
@@ -1399,16 +1439,19 @@ CHAT_TEMPLATE = '''
1399
  .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;}
1400
  .notification.show { opacity: 1;}
1401
  .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; }
 
1402
  .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
1403
  .chat-product-card-info { flex-grow: 1; }
1404
  .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
 
1405
  .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
1406
  .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
1407
  .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%; }
1408
  .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
 
1409
  </style>
1410
  </head>
1411
- <body>
1412
  <div class="chat-container">
1413
  <div class="chat-header">
1414
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
@@ -1796,7 +1839,7 @@ ORDER_TEMPLATE = '''
1796
  <head>
1797
  <meta charset="UTF-8">
1798
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1799
- <title>Заказ №{{ order.id }} - Gippo312</title>
1800
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600&display=swap" rel="stylesheet">
1801
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1802
  <style>
@@ -1931,7 +1974,7 @@ ORDER_TEMPLATE = '''
1931
  }
1932
  const orderId = document.getElementById('orderId').textContent;
1933
  const whatsappNumber = "{{ whatsapp_number }}".replace(/[^0-9]/g, '');
1934
- let message = `Здравствуйте! Хочу подтвердить или изменить свой заказ на Gippo312:%0A%0A`;
1935
  message += `*Номер заказа:* ${orderId}%0A%0A`;
1936
 
1937
  order.cart.forEach(item => {
@@ -1965,7 +2008,7 @@ ADMIN_TEMPLATE = '''
1965
  <head>
1966
  <meta charset="UTF-8">
1967
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1968
- <title>Админ-панель - Gippo312</title>
1969
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600&display=swap" rel="stylesheet">
1970
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1971
  <style>
@@ -2038,6 +2081,7 @@ ADMIN_TEMPLATE = '''
2038
  .status-indicator.in-stock { background-color: #d4edda; color: #155724; }
2039
  .status-indicator.out-of-stock { background-color: #f8d7da; color: #721c24; }
2040
  .status-indicator.top-product { background-color: #FFF9C4; color: #F57F17; margin-left: 5px;}
 
2041
  .ai-generate-button { background-color: #8D6EC8; color: white; margin-top: 5px; margin-bottom: 10px; }
2042
  .ai-generate-button:hover { background-color: #7B4DB5; }
2043
  .chat-log-item { padding: 10px; border: 1px solid #eee; border-radius: 5px; cursor: pointer; transition: background-color 0.2s; }
@@ -2057,8 +2101,8 @@ ADMIN_TEMPLATE = '''
2057
  <div class="container">
2058
  <div class="header">
2059
  <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
2060
- <img src="{{ chat_avatar_url }}" alt="Gippo312 Logo">
2061
- <h1><i class="fas fa-tools"></i> Админ-панель Gippo312 (Среда: {{ env_id }})</h1>
2062
  </div>
2063
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="button" style="background-color: var(--bg-medium); color: white;"><i class="fas fa-store"></i> Перейти в каталог</a>
2064
  </div>
@@ -2071,6 +2115,16 @@ ADMIN_TEMPLATE = '''
2071
  {% endif %}
2072
  {% endwith %}
2073
 
 
 
 
 
 
 
 
 
 
 
2074
  <div class="section">
2075
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
2076
  <div class="sync-buttons">
@@ -2092,6 +2146,9 @@ ADMIN_TEMPLATE = '''
2092
  <form method="POST" enctype="multipart/form-data">
2093
  <input type="hidden" name="action" value="update_settings">
2094
 
 
 
 
2095
  <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
2096
  <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.whatsapp_number }}" placeholder="+996XXXXXXXXX">
2097
 
@@ -2547,8 +2604,37 @@ def index():
2547
  @app.route('/admhosto', methods=['GET'])
2548
  def admhosto():
2549
  data = load_data()
2550
- environments = sorted(data.keys())
2551
- return render_template_string(ADMHOSTO_TEMPLATE, environments=environments)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2552
 
2553
  @app.route('/admhosto/create', methods=['POST'])
2554
  def create_environment():
@@ -2569,11 +2655,14 @@ def create_environment():
2569
  "contact": "Наш магазин находится по адресу: ... Связаться с нами можно по телефону ..."
2570
  },
2571
  'settings': {
 
2572
  "whatsapp_number": "+996701202013",
2573
  "currency_code": "KGS",
2574
  "chat_name": "EVA",
2575
  "chat_avatar": None,
2576
- "color_scheme": "default"
 
 
2577
  },
2578
  'chats': {}
2579
  }
@@ -2592,6 +2681,54 @@ def delete_environment(env_id):
2592
  flash(f'Среда {env_id} не найдена.', 'error')
2593
  return redirect(url_for('admhosto'))
2594
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2595
  @app.route('/<env_id>/catalog')
2596
  def catalog(env_id):
2597
  data = get_env_data(env_id)
@@ -2616,6 +2753,8 @@ def catalog(env_id):
2616
  ordered_categories = [cat for cat in all_cat_names if products_by_category.get(cat)]
2617
 
2618
  chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{settings['chat_avatar']}" if settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
 
 
2619
 
2620
  return render_template_string(
2621
  CATALOG_TEMPLATE,
@@ -2626,7 +2765,8 @@ def catalog(env_id):
2626
  currency_code=settings.get('currency_code', 'KGS'),
2627
  settings=settings,
2628
  chat_avatar_url=chat_avatar_url,
2629
- env_id=env_id
 
2630
  )
2631
 
2632
  @app.route('/<env_id>/product/<int:index>')
@@ -2717,6 +2857,7 @@ def view_order(env_id, order_id):
2717
  repo_id=REPO_ID,
2718
  currency_code=settings.get('currency_code', 'KGS'),
2719
  whatsapp_number=settings.get('whatsapp_number', ''),
 
2720
  env_id=env_id)
2721
 
2722
 
@@ -2774,6 +2915,7 @@ def admin(env_id):
2774
  flash("Информация о магазине успешно обновлена.", 'success')
2775
 
2776
  elif action == 'update_settings':
 
2777
  settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
2778
  settings['currency_code'] = request.form.get('currency_code', 'KGS')
2779
  settings['chat_name'] = request.form.get('chat_name', 'EVA').strip()
@@ -3035,6 +3177,23 @@ def admin(env_id):
3035
  display_chats = data.get('chats', {})
3036
  display_settings = data.get('settings', {})
3037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3038
  chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{display_settings['chat_avatar']}" if display_settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
3039
 
3040
  return render_template_string(
@@ -3049,7 +3208,8 @@ def admin(env_id):
3049
  chat_avatar_url=chat_avatar_url,
3050
  currencies=CURRENCIES,
3051
  color_schemes=COLOR_SCHEMES,
3052
- env_id=env_id
 
3053
  )
3054
 
3055
  @app.route('/generate_description_ai', methods=['POST'])
@@ -3072,6 +3232,9 @@ def handle_generate_description_ai():
3072
 
3073
  @app.route('/<env_id>/chat_with_ai', methods=['POST'])
3074
  def handle_chat_with_ai(env_id):
 
 
 
3075
  request_data = request.get_json()
3076
  user_message = request_data.get('message')
3077
  chat_history_from_client = request_data.get('history', [])
@@ -3102,6 +3265,9 @@ def handle_chat_with_ai(env_id):
3102
 
3103
  @app.route('/<env_id>/chat')
3104
  def chat_page(env_id):
 
 
 
3105
  data = get_env_data(env_id)
3106
  all_products_raw = data.get('products', [])
3107
  settings = data.get('settings', {})
 
6
  import logging
7
  import threading
8
  import time
9
+ from datetime import datetime, timedelta, timezone
10
  from uuid import uuid4
11
  import random
12
  import string
 
37
  DOWNLOAD_RETRIES = 3
38
  DOWNLOAD_DELAY = 5
39
 
40
+ ALMATY_TZ = timezone(timedelta(hours=6))
41
+
42
  CURRENCIES = {
43
  'KGS': 'Кыргызский сом',
44
  'KZT': 'Казахстанский тенге',
 
52
  'default': 'Бирюзовый (по умолч.)',
53
  'forest': 'Лесной зеленый',
54
  'ocean': 'Глубокий синий',
55
+ 'sunset': 'Закатный оранжевый',
56
+ 'lavender': 'Лавандовый',
57
+ 'vintage': 'Винтажный',
58
+ 'dark': 'Полночь (тёмная)'
59
  }
60
 
61
 
 
172
  except Exception as e:
173
  pass
174
 
175
+ def is_chat_active(env_id):
176
+ data = get_env_data(env_id)
177
+ settings = data.get('settings', {})
178
+
179
+ if not settings.get('chat_activated', False):
180
+ return False
181
+
182
+ expires_str = settings.get('chat_activation_expires')
183
+ if not expires_str:
184
+ return False
185
+
186
+ try:
187
+ expires_dt = datetime.fromisoformat(expires_str)
188
+ if expires_dt > datetime.now(ALMATY_TZ):
189
+ return True
190
+ except (ValueError, TypeError):
191
+ return False
192
+
193
+ return False
194
+
195
  def get_env_data(env_id):
196
  all_data = load_data()
197
  default_organization_info = {
198
+ "about_us": "Мы — надежный партнер в мире уникальных товаров. Мы предлагаем широкий ассортимент продукции, от электроники до товаров для дома, всегда стремясь к качеству и доступности. Наша миссия — сделать ваш шопинг приятным и удобным, предлагая только лучшие товары, тщательно отобранные для вас.",
199
  "shipping": "Доставка осуществляется по всему Кыргызстану. Стоимость и сроки доставки зависят от региона и веса товара. По Бишкеку доставка возможна в течение 1-2 рабочих дней, в регионы — от 3 до 7 дней. Для уточнения деталей свяжитесь с нами.",
200
  "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
201
  "contact": f"Наш магазин находится по адресу: Рынок Кербен, 6 ряд , 43 контейнер. Связаться с нами можно по телефону или через WhatsApp. Мы работаем ежедневно с 9:00 до 18:00."
202
  }
203
  default_settings = {
204
+ "organization_name": "Gippo312",
205
  "whatsapp_number": "+996701202013",
206
  "currency_code": "KGS",
207
  "chat_name": "EVA",
208
  "chat_avatar": None,
209
+ "color_scheme": "default",
210
+ "chat_activated": False,
211
+ "chat_activation_expires": None
212
  }
213
 
214
  env_data = all_data.get(env_id, {})
 
316
  raise ValueError(f"Ошибка при генерации контента: {e}")
317
 
318
  def generate_chat_response(message, chat_history_from_client, env_id):
319
+ if not is_chat_active(env_id):
320
+ return "Извините, чат в данный момент неактивен. Пожалуйста, свяжитесь с нами другим способом."
321
+
322
  if not configure_gemini():
323
  return "Извините, сервис чата временно недоступен. Пожалуйста, попробуйте позже."
324
 
 
329
  settings = data.get('settings', {})
330
  currency_code = settings.get('currency_code', 'KGS')
331
  chat_name = settings.get('chat_name', 'EVA')
332
+ org_name = settings.get('organization_name', 'Gippo312')
333
 
334
  product_info_list = []
335
  for p in products:
 
354
 
355
 
356
  system_instruction_content = (
357
+ f"Ты - доброжелательный и очень полезный виртуальный консультант по имени {chat_name} для магазина {org_name}. "
358
  "Твоя задача - помогать пользователям находить товары, отвечать на вопросы о них, предлагать варианты, а также предоставлять информацию о магазине. "
359
  "Всегда будь вежлив, информативен и стремись решить проблему пользователя. "
360
  "Никогда не выдумывай товары или категории, которых нет в предоставленных списках. "
 
436
  --text-dark: #333;
437
  --text-on-accent: #003C43;
438
  --danger: #E57373;
439
+ --warning: #ffcc80;
440
+ --warning-text: #856404;
441
  }
442
  body { font-family: 'Montserrat', sans-serif; background-color: var(--bg-light); color: var(--text-dark); padding: 20px; }
443
  .container { max-width: 900px; margin: 0 auto; background-color: #fff; padding: 25px; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.05); }
 
446
  .add-env-form { margin-bottom: 20px; text-align: center; }
447
  .button { padding: 10px 18px; border: none; border-radius: 6px; background-color: var(--accent); color: var(--text-on-accent); font-weight: 600; cursor: pointer; transition: background-color 0.3s ease; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; }
448
  .button:hover { background-color: var(--accent-hover); }
449
+ .button:disabled { background-color: #ccc; cursor: not-allowed; }
450
  .env-list { list-style: none; padding: 0; }
451
+ .env-item { background: #fdfdff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; margin-bottom: 10px; display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 15px; }
452
+ .env-details { display: flex; flex-direction: column; }
453
  .env-id { font-weight: 600; color: var(--bg-medium); font-size: 1.2rem; }
454
+ .env-status { font-size: 0.85rem; color: #666; }
455
+ .env-status .expires-soon { color: var(--danger); font-weight: bold; }
456
  .env-actions { display: flex; gap: 10px; flex-wrap: wrap; }
457
  .delete-button { background-color: var(--danger); color: white; }
458
+ .activate-button { background-color: #66BB6A; color: white; }
459
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; text-align: center; }
460
  .message.success { background-color: #d4edda; color: #155724; }
461
  .message.error { background-color: #f8d7da; color: #721c24; }
 
483
  <h2><i class="fas fa-list-ul"></i> Существующие среды</h2>
484
  {% if environments %}
485
  <ul class="env-list">
486
+ {% for env in environments %}
487
  <li class="env-item">
488
+ <div class="env-details">
489
+ <span class="env-id">{{ env.id }}</span>
490
+ <div class="env-status">
491
+ {% if env.chat_active %}
492
+ Активирован до: <span class="{{ 'expires-soon' if env.expires_soon else '' }}">{{ env.expires_date }}</span>
493
+ {% else %}
494
+ Чат не активирован
495
+ {% endif %}
496
+ </div>
497
+ </div>
498
  <div class="env-actions">
499
+ <a href="{{ url_for('admin', env_id=env.id) }}" class="button" target="_blank"><i class="fas fa-tools"></i> Админ</a>
500
+ <a href="{{ url_for('catalog', env_id=env.id) }}" class="button" target="_blank"><i class="fas fa-store"></i> Каталог</a>
501
+ <form method="POST" action="{{ url_for('activate_chat', env_id=env.id) }}" style="display:inline;">
502
+ <select name="period" style="padding: 5px; border-radius: 4px; border: 1px solid #ccc;">
503
+ <option value="month">1 месяц</option>
504
+ <option value="half_year">6 месяцев</option>
505
+ <option value="year">1 год</option>
506
+ </select>
507
+ <button type="submit" class="button activate-button">
508
+ <i class="fas fa-check-circle"></i> {{ 'Продлить' if env.chat_active else 'Активировать' }}
509
+ </button>
510
+ </form>
511
+ <form method="POST" action="{{ url_for('delete_environment', env_id=env.id) }}" style="display:inline;" onsubmit="return confirm('Вы уверены, что хотите удалить среду {{ env.id }}? Это действие необратимо.');">
512
  <button type="submit" class="button delete-button"><i class="fas fa-trash-alt"></i></button>
513
  </form>
514
  </div>
 
530
  <head>
531
  <meta charset="UTF-8">
532
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
533
+ <title>{{ settings.organization_name }} - Каталог</title>
534
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
535
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
536
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
537
  <style>
538
  {% if settings.color_scheme == 'forest' %}
539
  :root {
540
+ --bg-dark: #2F4F4F; --bg-medium: #556B2F; --accent: #90EE90; --accent-hover: #98FB98; --text-light: #F5F5DC; --text-dark: #333; --danger: #CD5C5C; --danger-hover: #F08080;
 
 
 
 
 
 
 
541
  }
542
  {% elif settings.color_scheme == 'ocean' %}
543
  :root {
544
+ --bg-dark: #000080; --bg-medium: #1E90FF; --accent: #87CEEB; --accent-hover: #ADD8E6; --text-light: #F0F8FF; --text-dark: #333; --danger: #FF6347; --danger-hover: #FF4500;
 
 
 
 
 
 
 
545
  }
546
  {% elif settings.color_scheme == 'sunset' %}
547
  :root {
548
+ --bg-dark: #8B4513; --bg-medium: #D2691E; --accent: #FFA500; --accent-hover: #FFD700; --text-light: #FFF8DC; --text-dark: #333; --danger: #DC143C; --danger-hover: #FF0000;
549
+ }
550
+ {% elif settings.color_scheme == 'lavender' %}
551
+ :root {
552
+ --bg-dark: #483D8B; --bg-medium: #9370DB; --accent: #E6E6FA; --accent-hover: #D8BFD8; --text-light: #F0F8FF; --text-dark: #333; --danger: #DB7093; --danger-hover: #FFC0CB;
553
+ }
554
+ {% elif settings.color_scheme == 'vintage' %}
555
+ :root {
556
+ --bg-dark: #5D4037; --bg-medium: #A1887F; --accent: #D7CCC8; --accent-hover: #EFEBE9; --text-light: #F5F5F5; --text-dark: #3E2723; --danger: #BF360C; --danger-hover: #F4511E;
557
+ }
558
+ {% elif settings.color_scheme == 'dark' %}
559
+ :root {
560
+ --bg-dark: #121212; --bg-medium: #1E1E1E; --accent: #BB86FC; --accent-hover: #A764FC; --text-light: #E1E1E1; --text-dark: #FFFFFF; --danger: #CF6679; --danger-hover: #D98899;
561
  }
562
  {% else %}
563
  :root {
564
+ --bg-dark: #003C43; --bg-medium: #135D66; --accent: #48D1CC; --accent-hover: #77E4D8; --text-light: #E3FEF7; --text-dark: #333; --danger: #E57373; --danger-hover: #EF5350;
 
 
 
 
 
 
 
565
  }
566
  {% endif %}
567
 
 
748
  }
749
  .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; }
750
  .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; }
751
+ .dark-theme .modal-content { background: #2a2a2a; color: var(--text-light); }
752
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
753
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
754
  .close:hover { color: #666; }
755
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
756
  .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; }
757
+ .dark-theme .cart-item { border-bottom-color: #444; }
758
  .cart-item:last-child { border-bottom: none; }
759
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
760
  .cart-item-details { grid-column: 2; }
761
  .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
762
  .cart-item-price { font-size: 0.9rem; color: #666; }
763
+ .dark-theme .cart-item-price { color: #ccc; }
764
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; grid-column: 3;}
765
  .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; font-size: 1.1rem; line-height: 1; display: flex; align-items: center; justify-content: center; }
766
+ .dark-theme .quantity-btn { background-color: #444; border-color: #555; color: #fff; }
767
  .cart-item-total { font-weight: bold; text-align: right; grid-column: 4; font-size: 1rem; color: var(--bg-medium);}
768
  .cart-item-remove { grid-column: 5; background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
769
  .cart-item-remove:hover { color: var(--danger-hover); }
770
  .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; box-sizing: border-box; }
771
+ .dark-theme .quantity-input, .dark-theme .color-select { background-color: #333; color: #fff; border-color: #555; }
772
  .quantity-input:focus, .color-select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px rgba(72, 209, 204, 0.2); }
773
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
774
+ .dark-theme .cart-summary { border-top-color: #444; }
775
  .cart-summary strong { font-size: 1.2rem; color: var(--bg-medium);}
776
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
777
  .product-button { display: block; width: auto; flex-grow: 1; padding: 10px; border: none; border-radius: 8px; color: white; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; text-align: center; text-decoration: none; }
 
793
 
794
  </style>
795
  </head>
796
+ <body class="{{ 'dark-theme' if settings.color_scheme == 'dark' else '' }}">
797
  <div class="container">
798
  <div class="top-bar">
799
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="logo">
 
831
  alt="{{ product.name }}"
832
  loading="lazy">
833
  {% else %}
834
+ <img src="https://via.placeholder.com/170x170.png?text={{ settings.organization_name }}" alt="No Image" loading="lazy">
835
  {% endif %}
836
  </div>
837
  <div class="product-info-overlay">
 
890
  </div>
891
 
892
  <div class="floating-buttons-container">
893
+ {% if chat_is_active %}
894
  <a href="{{ url_for('chat_page', env_id=env_id) }}" id="chat-open-button" class="floating-button" aria-label="Открыть чат">
895
  <i class="fas fa-comment-dots"></i>
896
  </a>
897
+ {% endif %}
898
  <button id="cart-button" class="floating-button" onclick="openCartModal()" aria-label="Открыть корзину">
899
  <i class="fas fa-shopping-cart"></i>
900
  <span id="cart-count">0</span>
 
1258
  <head>
1259
  <meta charset="UTF-8">
1260
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
1261
+ <title>{{ settings.organization_name }} - Чат с {{ settings.chat_name }}</title>
1262
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1263
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
1264
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1265
  <style>
1266
  {% if settings.color_scheme == 'forest' %}
1267
  :root {
1268
+ --bg-dark: #2F4F4F; --bg-medium: #556B2F; --accent: #90EE90; --accent-hover: #98FB98; --text-light: #F5F5DC; --text-dark: #333; --danger: #CD5C5C; --danger-hover: #F08080; --chat-bg: #F5F5DC;
 
 
 
 
 
 
 
 
1269
  }
1270
  {% elif settings.color_scheme == 'ocean' %}
1271
  :root {
1272
+ --bg-dark: #000080; --bg-medium: #1E90FF; --accent: #87CEEB; --accent-hover: #ADD8E6; --text-light: #F0F8FF; --text-dark: #333; --danger: #FF6347; --danger-hover: #FF4500; --chat-bg: #F0F8FF;
 
 
 
 
 
 
 
 
1273
  }
1274
  {% elif settings.color_scheme == 'sunset' %}
1275
  :root {
1276
+ --bg-dark: #8B4513; --bg-medium: #D2691E; --accent: #FFA500; --accent-hover: #FFD700; --text-light: #FFF8DC; --text-dark: #333; --danger: #DC143C; --danger-hover: #FF0000; --chat-bg: #FFF8DC;
1277
+ }
1278
+ {% elif settings.color_scheme == 'lavender' %}
1279
+ :root {
1280
+ --bg-dark: #483D8B; --bg-medium: #9370DB; --accent: #E6E6FA; --accent-hover: #D8BFD8; --text-light: #F0F8FF; --text-dark: #333; --danger: #DB7093; --danger-hover: #FFC0CB; --chat-bg: #F8F8FF;
1281
+ }
1282
+ {% elif settings.color_scheme == 'vintage' %}
1283
+ :root {
1284
+ --bg-dark: #5D4037; --bg-medium: #A1887F; --accent: #D7CCC8; --accent-hover: #EFEBE9; --text-light: #F5F5F5; --text-dark: #3E2723; --danger: #BF360C; --danger-hover: #F4511E; --chat-bg: #EFEBE9;
1285
+ }
1286
+ {% elif settings.color_scheme == 'dark' %}
1287
+ :root {
1288
+ --bg-dark: #1F1F1F; --bg-medium: #333333; --accent: #BB86FC; --accent-hover: #A764FC; --text-light: #E1E1E1; --text-dark: #FFFFFF; --danger: #CF6679; --danger-hover: #D98899; --chat-bg: #121212;
1289
  }
1290
  {% else %}
1291
  :root {
1292
+ --bg-dark: #003C43; --bg-medium: #135D66; --accent: #48D1CC; --accent-hover: #77E4D8; --text-light: #E3FEF7; --text-dark: #333; --danger: #E57373; --danger-hover: #EF5350; --chat-bg: #f0f2f5;
 
 
 
 
 
 
 
 
1293
  }
1294
  {% endif %}
1295
 
 
1314
  background: #fff;
1315
  box-shadow: 0 0 20px rgba(0,0,0,0.05);
1316
  }
1317
+ body.dark-theme .chat-container { background: var(--chat-bg); color: var(--text-light); }
1318
  .chat-header {
1319
  display: flex;
1320
  align-items: center;
 
1362
  color: var(--text-dark);
1363
  border-bottom-left-radius: 4px;
1364
  }
1365
+ body.dark-theme .chat-message.ai .message-bubble { background-color: #333; color: var(--text-light); }
1366
  .chat-input-container {
1367
  padding: 15px;
1368
  background: #fff;
 
1372
  align-items: center;
1373
  flex-shrink: 0;
1374
  }
1375
+ body.dark-theme .chat-input-container { background: var(--bg-dark); border-top: 1px solid #444;}
1376
  #chat-input {
1377
  flex-grow: 1;
1378
  padding: 12px 18px;
 
1382
  outline: none;
1383
  transition: border-color 0.3s, box-shadow 0.3s;
1384
  }
1385
+ body.dark-theme #chat-input { background-color: #333; color: var(--text-light); border-color: #555; }
1386
  #chat-input:focus {
1387
  border-color: var(--bg-medium);
1388
  box-shadow: 0 0 0 3px rgba(19, 93, 102, 0.15);
 
1412
  #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); }
1413
  .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; }
1414
  .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; }
1415
+ body.dark-theme .modal-content { background-color: #2a2a2a; color: var(--text-light); }
1416
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
1417
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
1418
  .close:hover { color: #666; }
1419
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
1420
  .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; }
1421
+ body.dark-theme .cart-item { border-bottom-color: #444; }
1422
  .cart-item:last-child { border-bottom: none; }
1423
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
1424
  .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
1425
+ body.dark-theme .cart-item-details strong { color: var(--text-light); }
1426
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; }
1427
  .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; }
1428
+ body.dark-theme .quantity-btn { background-color: #444; border-color: #555; color: #fff; }
1429
  .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--bg-medium);}
1430
  .cart-item-remove { background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; }
1431
  .quantity-input, .color-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; }
1432
+ body.dark-theme .quantity-input, body.dark-theme .color-select { background-color: #333; color: #fff; border-color: #555; }
1433
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
1434
+ body.dark-theme .cart-summary { border-top-color: #444; }
1435
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; }
1436
  .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; }
1437
  .clear-cart { background-color: #6c757d; }
 
1439
  .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;}
1440
  .notification.show { opacity: 1;}
1441
  .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; }
1442
+ body.dark-theme .chat-product-card { background-color: #333; border-color: #555; }
1443
  .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
1444
  .chat-product-card-info { flex-grow: 1; }
1445
  .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
1446
+ body.dark-theme .chat-product-card-info strong { color: var(--text-light); }
1447
  .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
1448
  .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
1449
  .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%; }
1450
  .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
1451
+ body.dark-theme .chat-product-link, body.dark-theme .chat-add-to-cart { background-color: #444; color: var(--accent); }
1452
  </style>
1453
  </head>
1454
+ <body class="{{ 'dark-theme' if settings.color_scheme == 'dark' else '' }}">
1455
  <div class="chat-container">
1456
  <div class="chat-header">
1457
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
 
1839
  <head>
1840
  <meta charset="UTF-8">
1841
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1842
+ <title>Заказ №{{ order.id }} - {{ settings.organization_name }}</title>
1843
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600&display=swap" rel="stylesheet">
1844
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1845
  <style>
 
1974
  }
1975
  const orderId = document.getElementById('orderId').textContent;
1976
  const whatsappNumber = "{{ whatsapp_number }}".replace(/[^0-9]/g, '');
1977
+ let message = `Здравствуйте! Хочу подтвердить или изменить свой заказ в магазине {{ settings.organization_name }}:%0A%0A`;
1978
  message += `*Номер заказа:* ${orderId}%0A%0A`;
1979
 
1980
  order.cart.forEach(item => {
 
2008
  <head>
2009
  <meta charset="UTF-8">
2010
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2011
+ <title>Админ-панель - {{ settings.organization_name }}</title>
2012
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600&display=swap" rel="stylesheet">
2013
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
2014
  <style>
 
2081
  .status-indicator.in-stock { background-color: #d4edda; color: #155724; }
2082
  .status-indicator.out-of-stock { background-color: #f8d7da; color: #721c24; }
2083
  .status-indicator.top-product { background-color: #FFF9C4; color: #F57F17; margin-left: 5px;}
2084
+ .status-indicator.expires-soon { background-color: var(--danger); color: white; }
2085
  .ai-generate-button { background-color: #8D6EC8; color: white; margin-top: 5px; margin-bottom: 10px; }
2086
  .ai-generate-button:hover { background-color: #7B4DB5; }
2087
  .chat-log-item { padding: 10px; border: 1px solid #eee; border-radius: 5px; cursor: pointer; transition: background-color 0.2s; }
 
2101
  <div class="container">
2102
  <div class="header">
2103
  <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
2104
+ <img src="{{ chat_avatar_url }}" alt="Logo">
2105
+ <h1><i class="fas fa-tools"></i> Админ-панель {{ settings.organization_name }} (Среда: {{ env_id }})</h1>
2106
  </div>
2107
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="button" style="background-color: var(--bg-medium); color: white;"><i class="fas fa-store"></i> Перейти в каталог</a>
2108
  </div>
 
2115
  {% endif %}
2116
  {% endwith %}
2117
 
2118
+ <div class="section">
2119
+ <h2><i class="fas fa-robot"></i> Статус активации чата</h2>
2120
+ {% if chat_status.active %}
2121
+ <p>Чат <strong>активен</strong>. Срок действия истекает: <strong class="{{ 'status-indicator expires-soon' if chat_status.expires_soon else '' }}">{{ chat_status.expires_date }}</strong></p>
2122
+ {% else %}
2123
+ <p>Чат <strong>неактивен</strong>. {% if chat_status.expires_date %}Срок действия истек: {{ chat_status.expires_date }}{% endif %}</p>
2124
+ <p style="color: var(--danger);">Для возобновления работы чат-бота, активируйте его в <a href="{{ url_for('admhosto') }}">главной админ-панели</a>.</p>
2125
+ {% endif %}
2126
+ </div>
2127
+
2128
  <div class="section">
2129
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
2130
  <div class="sync-buttons">
 
2146
  <form method="POST" enctype="multipart/form-data">
2147
  <input type="hidden" name="action" value="update_settings">
2148
 
2149
+ <label for="organization_name">Название организации:</label>
2150
+ <input type="text" id="organization_name" name="organization_name" value="{{ settings.organization_name }}">
2151
+
2152
  <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
2153
  <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.whatsapp_number }}" placeholder="+996XXXXXXXXX">
2154
 
 
2604
  @app.route('/admhosto', methods=['GET'])
2605
  def admhosto():
2606
  data = load_data()
2607
+ environments_data = []
2608
+
2609
+ for env_id, env_data in data.items():
2610
+ settings = env_data.get('settings', {})
2611
+ is_active = False
2612
+ expires_soon = False
2613
+ expires_date_str = "N/A"
2614
+
2615
+ if settings.get('chat_activated', False):
2616
+ expires_str = settings.get('chat_activation_expires')
2617
+ if expires_str:
2618
+ try:
2619
+ expires_dt = datetime.fromisoformat(expires_str)
2620
+ now_almaty = datetime.now(ALMATY_TZ)
2621
+ if expires_dt > now_almaty:
2622
+ is_active = True
2623
+ expires_date_str = expires_dt.strftime('%Y-%m-%d')
2624
+ if (expires_dt - now_almaty).days <= 4:
2625
+ expires_soon = True
2626
+ except (ValueError, TypeError):
2627
+ pass
2628
+
2629
+ environments_data.append({
2630
+ "id": env_id,
2631
+ "chat_active": is_active,
2632
+ "expires_soon": expires_soon,
2633
+ "expires_date": expires_date_str
2634
+ })
2635
+
2636
+ environments_data.sort(key=lambda x: x['id'])
2637
+ return render_template_string(ADMHOSTO_TEMPLATE, environments=environments_data)
2638
 
2639
  @app.route('/admhosto/create', methods=['POST'])
2640
  def create_environment():
 
2655
  "contact": "Наш магазин находится по адресу: ... Связаться с нами можно по телефону ..."
2656
  },
2657
  'settings': {
2658
+ "organization_name": "Gippo312",
2659
  "whatsapp_number": "+996701202013",
2660
  "currency_code": "KGS",
2661
  "chat_name": "EVA",
2662
  "chat_avatar": None,
2663
+ "color_scheme": "default",
2664
+ "chat_activated": False,
2665
+ "chat_activation_expires": None
2666
  },
2667
  'chats': {}
2668
  }
 
2681
  flash(f'Среда {env_id} не найдена.', 'error')
2682
  return redirect(url_for('admhosto'))
2683
 
2684
+ @app.route('/admhosto/activate/<env_id>', methods=['POST'])
2685
+ def activate_chat(env_id):
2686
+ period = request.form.get('period')
2687
+
2688
+ if not period:
2689
+ flash('Не выбран период активации.', 'error')
2690
+ return redirect(url_for('admhosto'))
2691
+
2692
+ delta = None
2693
+ if period == 'month':
2694
+ delta = timedelta(days=30)
2695
+ elif period == 'half_year':
2696
+ delta = timedelta(days=182)
2697
+ elif period == 'year':
2698
+ delta = timedelta(days=365)
2699
+
2700
+ if not delta:
2701
+ flash('Неверный период активации.', 'error')
2702
+ return redirect(url_for('admhosto'))
2703
+
2704
+ data = get_env_data(env_id)
2705
+ if not data:
2706
+ flash(f'Среда {env_id} не найдена.', 'error')
2707
+ return redirect(url_for('admhosto'))
2708
+
2709
+ settings = data.get('settings', {})
2710
+ now_almaty = datetime.now(ALMATY_TZ)
2711
+
2712
+ start_date = now_almaty
2713
+ expires_str = settings.get('chat_activation_expires')
2714
+ if expires_str:
2715
+ try:
2716
+ current_expires_dt = datetime.fromisoformat(expires_str)
2717
+ if current_expires_dt > now_almaty:
2718
+ start_date = current_expires_dt
2719
+ except (ValueError, TypeError):
2720
+ pass
2721
+
2722
+ new_expires_date = start_date + delta
2723
+
2724
+ settings['chat_activated'] = True
2725
+ settings['chat_activation_expires'] = new_expires_date.isoformat()
2726
+ data['settings'] = settings
2727
+
2728
+ save_env_data(env_id, data)
2729
+ flash(f'Чат для среды {env_id} активирован/продлен до {new_expires_date.strftime("%Y-%m-%d")}.', 'success')
2730
+ return redirect(url_for('admhosto'))
2731
+
2732
  @app.route('/<env_id>/catalog')
2733
  def catalog(env_id):
2734
  data = get_env_data(env_id)
 
2753
  ordered_categories = [cat for cat in all_cat_names if products_by_category.get(cat)]
2754
 
2755
  chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{settings['chat_avatar']}" if settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
2756
+
2757
+ chat_active = is_chat_active(env_id)
2758
 
2759
  return render_template_string(
2760
  CATALOG_TEMPLATE,
 
2765
  currency_code=settings.get('currency_code', 'KGS'),
2766
  settings=settings,
2767
  chat_avatar_url=chat_avatar_url,
2768
+ env_id=env_id,
2769
+ chat_is_active=chat_active
2770
  )
2771
 
2772
  @app.route('/<env_id>/product/<int:index>')
 
2857
  repo_id=REPO_ID,
2858
  currency_code=settings.get('currency_code', 'KGS'),
2859
  whatsapp_number=settings.get('whatsapp_number', ''),
2860
+ settings=settings,
2861
  env_id=env_id)
2862
 
2863
 
 
2915
  flash("Информация о магазине успешно обновлена.", 'success')
2916
 
2917
  elif action == 'update_settings':
2918
+ settings['organization_name'] = request.form.get('organization_name', 'Gippo312').strip()
2919
  settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
2920
  settings['currency_code'] = request.form.get('currency_code', 'KGS')
2921
  settings['chat_name'] = request.form.get('chat_name', 'EVA').strip()
 
3177
  display_chats = data.get('chats', {})
3178
  display_settings = data.get('settings', {})
3179
 
3180
+ chat_status = { "active": False, "expires_soon": False, "expires_date": "N/A" }
3181
+ if display_settings.get('chat_activated'):
3182
+ expires_str = display_settings.get('chat_activation_expires')
3183
+ if expires_str:
3184
+ try:
3185
+ expires_dt = datetime.fromisoformat(expires_str)
3186
+ now_almaty = datetime.now(ALMATY_TZ)
3187
+ if expires_dt > now_almaty:
3188
+ chat_status["active"] = True
3189
+ chat_status["expires_date"] = expires_dt.strftime('%Y-%m-%d %H:%M:%S')
3190
+ if (expires_dt - now_almaty).days <= 4:
3191
+ chat_status["expires_soon"] = True
3192
+ else:
3193
+ chat_status["expires_date"] = expires_dt.strftime('%Y-%m-%d %H:%M:%S')
3194
+ except (ValueError, TypeError):
3195
+ pass
3196
+
3197
  chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{display_settings['chat_avatar']}" if display_settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
3198
 
3199
  return render_template_string(
 
3208
  chat_avatar_url=chat_avatar_url,
3209
  currencies=CURRENCIES,
3210
  color_schemes=COLOR_SCHEMES,
3211
+ env_id=env_id,
3212
+ chat_status=chat_status
3213
  )
3214
 
3215
  @app.route('/generate_description_ai', methods=['POST'])
 
3232
 
3233
  @app.route('/<env_id>/chat_with_ai', methods=['POST'])
3234
  def handle_chat_with_ai(env_id):
3235
+ if not is_chat_active(env_id):
3236
+ return jsonify({"error": "Чат неактивен."}), 403
3237
+
3238
  request_data = request.get_json()
3239
  user_message = request_data.get('message')
3240
  chat_history_from_client = request_data.get('history', [])
 
3265
 
3266
  @app.route('/<env_id>/chat')
3267
  def chat_page(env_id):
3268
+ if not is_chat_active(env_id):
3269
+ return "Чат для этой среды неактивен. Обратитесь к администратору.", 403
3270
+
3271
  data = get_env_data(env_id)
3272
  all_products_raw = data.get('products', [])
3273
  settings = data.get('settings', {})