Kgshop commited on
Commit
298111d
·
verified ·
1 Parent(s): baae1a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +374 -396
app.py CHANGED
@@ -64,6 +64,12 @@ COLOR_SCHEMES = {
64
  'cyberpunk': 'Киберпанк неон'
65
  }
66
 
 
 
 
 
 
 
67
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
68
 
69
  def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY):
@@ -213,7 +219,8 @@ def get_env_data(env_id):
213
  "chat_avatar": None,
214
  "color_scheme": "default",
215
  "chat_activated": False,
216
- "chat_activation_expires": None
 
217
  }
218
 
219
  env_data = all_data.get(env_id, {})
@@ -248,6 +255,12 @@ def get_env_data(env_id):
248
  if 'variant_prices' not in product:
249
  product['variant_prices'] = {}
250
  products_changed = True
 
 
 
 
 
 
251
 
252
  if products_changed or settings_changed:
253
  save_env_data(env_id, env_data)
@@ -341,12 +354,17 @@ def generate_chat_response(message, chat_history_from_client, env_id):
341
  currency_code = settings.get('currency_code', 'KGS')
342
  chat_name = settings.get('chat_name', 'EVA')
343
  org_name = settings.get('organization_name', 'Gippo312')
 
344
 
345
  product_info_list = []
346
  for p in products:
347
  if p.get('in_stock', True):
348
  price_display = f"{p.get('price', 0):.2f}".replace('.00', '')
349
-
 
 
 
 
350
  colors_str = f"Цвета: {', '.join(p.get('colors', []))}" if p.get('colors') else ""
351
  sizes_str = f"Размеры: {', '.join(p.get('sizes', []))}" if p.get('sizes') else ""
352
 
@@ -360,11 +378,13 @@ def generate_chat_response(message, chat_history_from_client, env_id):
360
  if variant_prices_list:
361
  variant_prices_str = f", Особые цены: [{'; '.join(variant_prices_list)}]"
362
 
 
 
363
  product_info_list.append(
364
  f"- [ID_ТОВАРА: {p.get('product_id', 'N/A')} Название: {p.get('name', 'Без названия')}], "
365
  f"Категория: {p.get('category', 'Без категории')}, "
366
- f"Базовая цена: {price_display} {currency_code}"
367
- f"{options_str}{variant_prices_str}, "
368
  f"Описание: {p.get('description', '')[:100]}..."
369
  )
370
  product_list_str = "\n".join(product_info_list) if product_info_list else "В данный момент нет товаров в наличии."
@@ -383,18 +403,45 @@ def generate_chat_response(message, chat_history_from_client, env_id):
383
  if organization_info.get("contact"):
384
  org_info_str += f"Контактная информация: {organization_info['contact']}\n"
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
 
 
387
  system_instruction_content = (
388
- f"Ты — первоклассный виртуальный консультант-продажник по имени {chat_name} для магазина {org_name}. Твоя главная цель — не просто отвечать на вопросы, а продавать, используя все свои навыки. "
389
- "Говори на любом языке, на котором к тебе обращается клиент. Будь энергичным, убедительным и проактивным. "
390
- "Твоя задача — помогать пользователям находить товары, мастерски отвечать на вопросы о них, предлагать лучшие варианты, создавать ценность и закрывать сделку. "
391
- "Всегда будь вежлив, но настойчив. Твоя речь должна быть живой, с использованием эмодзи, чтобы располагать к себе клиента. "
392
- "Никогда не выдумывай товары, категории или характеристики, которых нет в предоставленных списках. "
393
- "Когда ты предлагаешь товар, всегда указывай его название и ID, используя *точный формат*: [ID_ТОВАРА: <product_id> Название: <product_name>]. Это *критически важно* для клиента. "
394
- "Если пользователь спрашивает цену на конкретный вариант (цвет или размер), найди ее в 'Особые цены'. Если там нет, используй 'Базовая цена'. "
395
- "Активно предлагай сопутствующие товары или более дорогие аналоги (апсейл). Например: 'Отличный выбор! К этому телефону идеально подойдут наши новые беспроводные наушники. Хотите взглянуть?'. "
396
- "Создавай ощущение срочности: 'Эта модель сейчас очень популярна, осталось всего несколько штук!'. "
397
- "Работай с возражениями: если клиент говорит 'дорого', расскажи о качестве, гарантии и уникальных особенностях товара.\n\n"
398
  f"Список доступных категорий: {category_list_str}.\n\n"
399
  f"Список доступных товаров в магазине (используй эту информацию для ответов):\n"
400
  f"{product_list_str}"
@@ -505,31 +552,32 @@ ADMHOSTO_TEMPLATE = '''
505
  .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); }
506
  h1 { font-weight: 600; color: var(--bg-medium); margin-bottom: 25px; text-align: center; }
507
  .section { margin-bottom: 30px; }
508
- .add-env-form { margin-bottom: 20px; text-align: center; }
509
- #search-env {
510
- width: 100%;
511
- padding: 10px;
512
- border: 1px solid #ddd;
513
- border-radius: 6px;
514
- box-sizing: border-box;
515
- font-size: 1rem;
516
- font-family: 'Montserrat', sans-serif;
517
- }
518
  .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; }
519
  .button:hover { background-color: var(--accent-hover); }
520
  .button:disabled { background-color: #ccc; cursor: not-allowed; }
521
  .env-list { list-style: none; padding: 0; }
522
  .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; }
523
- .env-details { display: flex; flex-direction: column; }
524
  .env-id { font-weight: 600; color: var(--bg-medium); font-size: 1.2rem; }
 
525
  .env-status { font-size: 0.85rem; color: #666; }
526
  .env-status .expires-soon { color: var(--danger); font-weight: bold; }
 
527
  .env-actions { display: flex; gap: 10px; flex-wrap: wrap; }
528
  .delete-button { background-color: var(--danger); color: white; }
529
  .activate-button { background-color: #66BB6A; color: white; }
530
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; text-align: center; }
531
  .message.success { background-color: #d4edda; color: #155724; }
532
  .message.error { background-color: #f8d7da; color: #721c24; }
 
 
 
 
 
 
533
  </style>
534
  </head>
535
  <body>
@@ -546,6 +594,12 @@ ADMHOSTO_TEMPLATE = '''
546
 
547
  <div class="section">
548
  <form method="POST" action="{{ url_for('create_environment') }}" class="add-env-form">
 
 
 
 
 
 
549
  <button type="submit" class="button"><i class="fas fa-plus-circle"></i> Создать новую среду</button>
550
  </form>
551
  </div>
@@ -562,18 +616,21 @@ ADMHOSTO_TEMPLATE = '''
562
  <li class="env-item">
563
  <div class="env-details">
564
  <span class="env-id">{{ env.id }}</span>
565
- <div class="env-status">
566
- {% if env.chat_active %}
567
- Активирован до: <span class="{{ 'expires-soon' if env.expires_soon else '' }}">{{ env.expires_date }}</span>
568
- {% else %}
569
- Чат не активирован
570
- {% endif %}
 
 
 
571
  </div>
572
  </div>
573
  <div class="env-actions">
574
  <a href="{{ url_for('admin', env_id=env.id) }}" class="button" target="_blank"><i class="fas fa-tools"></i> Админ</a>
575
  <a href="{{ url_for('catalog', env_id=env.id) }}" class="button" target="_blank"><i class="fas fa-store"></i> Каталог</a>
576
- <form method="POST" action="{{ url_for('activate_chat', env_id=env.id) }}" style="display:inline;">
577
  <select name="period" style="padding: 5px; border-radius: 4px; border: 1px solid #ccc;">
578
  <option value="month">1 месяц</option>
579
  <option value="half_year">6 месяцев</option>
@@ -625,31 +682,31 @@ CATALOG_TEMPLATE = '''
625
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
626
  <style>
627
  {% if settings.color_scheme == 'forest' %}
628
- :root { --bg-dark: #2F4F4F; --bg-medium: #556B2F; --accent: #90EE90; --accent-hover: #98FB98; --text-light: #F5F5DC; --text-dark: #333; --danger: #CD5C5C; --danger-hover: #F08080; }
629
  {% elif settings.color_scheme == 'ocean' %}
630
- :root { --bg-dark: #000080; --bg-medium: #1E90FF; --accent: #87CEEB; --accent-hover: #ADD8E6; --text-light: #F0F8FF; --text-dark: #333; --danger: #FF6347; --danger-hover: #FF4500; }
631
  {% elif settings.color_scheme == 'sunset' %}
632
- :root { --bg-dark: #8B4513; --bg-medium: #D2691E; --accent: #FFA500; --accent-hover: #FFD700; --text-light: #FFF8DC; --text-dark: #333; --danger: #DC143C; --danger-hover: #FF0000; }
633
  {% elif settings.color_scheme == 'lavender' %}
634
- :root { --bg-dark: #483D8B; --bg-medium: #9370DB; --accent: #E6E6FA; --accent-hover: #D8BFD8; --text-light: #F0F8FF; --text-dark: #333; --danger: #DB7093; --danger-hover: #FFC0CB; }
635
  {% elif settings.color_scheme == 'vintage' %}
636
- :root { --bg-dark: #5D4037; --bg-medium: #A1887F; --accent: #D7CCC8; --accent-hover: #EFEBE9; --text-light: #F5F5F5; --text-dark: #3E2723; --danger: #BF360C; --danger-hover: #F4511E; }
637
  {% elif settings.color_scheme == 'dark' %}
638
- :root { --bg-dark: #121212; --bg-medium: #1E1E1E; --accent: #BB86FC; --accent-hover: #A764FC; --text-light: #E1E1E1; --text-dark: #FFFFFF; --danger: #CF6679; --danger-hover: #D98899; }
639
  {% elif settings.color_scheme == 'cosmic' %}
640
- :root { --bg-dark: #2c003e; --bg-medium: #4a0072; --accent: #ff00c1; --accent-hover: #ff5fd8; --text-light: #f3e5f5; --text-dark: #ffffff; --danger: #e91e63; --danger-hover: #f06292; }
641
  {% elif settings.color_scheme == 'minty' %}
642
- :root { --bg-dark: #64B5F6; --bg-medium: #81D4FA; --accent: #B2EBF2; --accent-hover: #E0F7FA; --text-light: #000000; --text-dark: #000000; --danger: #ef5350; --danger-hover: #e57373; }
643
  {% elif settings.color_scheme == 'mocha' %}
644
- :root { --bg-dark: #3e2723; --bg-medium: #5d4037; --accent: #a1887f; --accent-hover: #bcaaa4; --text-light: #efebe9; --text-dark: #ffffff; --danger: #d32f2f; --danger-hover: #e57373; }
645
  {% elif settings.color_scheme == 'crimson' %}
646
- :root { --bg-dark: #121212; --bg-medium: #b71c1c; --accent: #f44336; --accent-hover: #ef5350; --text-light: #ffffff; --text-dark: #ffffff; --danger: #ff5252; --danger-hover: #ff8a80; }
647
  {% elif settings.color_scheme == 'solar' %}
648
- :root { --bg-dark: #ff6f00; --bg-medium: #ff8f00; --accent: #ffca28; --accent-hover: #ffd54f; --text-light: #212121; --text-dark: #212121; --danger: #d32f2f; --danger-hover: #e57373; }
649
  {% elif settings.color_scheme == 'cyberpunk' %}
650
- :root { --bg-dark: #000000; --bg-medium: #0d0221; --accent: #00f0ff; --accent-hover: #81f5ff; --text-light: #ffffff; --text-dark: #ffffff; --danger: #f50057; --danger-hover: #ff4081; }
651
  {% else %}
652
- :root { --bg-dark: #003C43; --bg-medium: #135D66; --accent: #48D1CC; --accent-hover: #77E4D8; --text-light: #E3FEF7; --text-dark: #333; --danger: #E57373; --danger-hover: #EF5350; }
653
  {% endif %}
654
 
655
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -657,7 +714,7 @@ CATALOG_TEMPLATE = '''
657
  body {
658
  font-family: 'Montserrat', sans-serif;
659
  background-color: var(--bg-dark);
660
- color: var(--text-light);
661
  line-height: 1.6;
662
  }
663
  .container {
@@ -676,212 +733,82 @@ CATALOG_TEMPLATE = '''
676
  z-index: 999;
677
  border-bottom: 1px solid var(--bg-medium);
678
  }
679
- .logo {
680
- flex-shrink: 0;
681
- }
682
- .logo img {
683
- width: 45px;
684
- height: 45px;
685
- border-radius: 50%;
686
- border: 2px solid var(--accent);
687
- }
688
- .search-wrapper {
689
- flex-grow: 1;
690
- position: relative;
691
- }
692
- #search-input {
693
- width: 100%;
694
- padding: 12px 20px 12px 45px;
695
- font-size: 1rem;
696
- border: none;
697
- border-radius: 25px;
698
- outline: none;
699
- background-color: var(--bg-medium);
700
- color: var(--text-light);
701
- transition: all 0.3s ease;
702
- }
703
- #search-input::placeholder { color: rgba(227, 254, 247, 0.6); }
704
- .search-wrapper .fa-search {
705
- position: absolute;
706
- top: 50%;
707
- left: 18px;
708
- transform: translateY(-50%);
709
- color: rgba(227, 254, 247, 0.6);
710
- font-size: 1rem;
711
- }
712
- .category-section {
713
- margin-top: 20px;
714
- }
715
- .category-header {
716
- display: flex;
717
- justify-content: space-between;
718
- align-items: center;
719
- padding: 0 20px;
720
- margin-bottom: 15px;
721
- }
722
- .category-header h2 {
723
- font-size: 1.5rem;
724
- font-weight: 600;
725
- }
726
- .category-header .view-all-arrow {
727
- font-size: 1.8rem;
728
- color: var(--accent);
729
- text-decoration: none;
730
- font-weight: 300;
731
- }
732
- .product-carousel {
733
- display: flex;
734
- overflow-x: auto;
735
- gap: 16px;
736
- padding: 10px 20px;
737
- -webkit-overflow-scrolling: touch;
738
- scrollbar-width: none;
739
- }
740
  .product-carousel::-webkit-scrollbar { display: none; }
741
- .product-card {
742
- width: 170px;
743
- height: 170px;
744
- background: white;
745
- border-radius: 16px;
746
- flex-shrink: 0;
747
- overflow: hidden;
748
- cursor: pointer;
749
- transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
750
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
751
- position: relative;
752
- }
753
- .product-card:hover {
754
- transform: translateY(-5px);
755
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
756
- }
757
  .product-card:active { transform: scale(0.96); }
758
- .product-image-container {
759
- width: 100%;
760
- height: 100%;
761
- }
762
- .product-image-container img {
763
- width: 100%;
764
- height: 100%;
765
- object-fit: cover;
766
- }
767
- .product-info-overlay {
768
- position: absolute;
769
- bottom: 0;
770
- left: 0;
771
- right: 0;
772
- background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, transparent 100%);
773
- padding: 20px 12px 10px;
774
- color: #fff;
775
- pointer-events: none;
776
- }
777
- .product-price {
778
- font-size: 1rem;
779
- font-weight: 600;
780
- }
781
- .top-product-indicator {
782
- position: absolute;
783
- top: 8px;
784
- right: 8px;
785
- background-color: var(--accent);
786
- color: var(--bg-dark);
787
- padding: 2px 8px;
788
- font-size: 0.7rem;
789
- border-radius: 10px;
790
- font-weight: bold;
791
- z-index: 10;
792
- }
793
  .no-results-message { padding: 40px 20px; text-align: center; font-size: 1.1rem; color: #ccc; }
794
- .floating-buttons-container {
795
- position: fixed;
796
- bottom: 25px;
797
- right: 25px;
798
- display: flex;
799
- flex-direction: column;
800
- gap: 15px;
801
- z-index: 1000;
802
- }
803
- .floating-button {
804
- background-color: var(--accent);
805
- color: var(--bg-dark);
806
- border: none;
807
- border-radius: 50%;
808
- width: 55px;
809
- height: 55px;
810
- font-size: 1.5rem;
811
- cursor: pointer;
812
- display: flex;
813
- align-items: center;
814
- justify-content: center;
815
- box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4);
816
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
817
- text-decoration: none;
818
- }
819
- .floating-button:hover {
820
- background-color: var(--accent-hover);
821
- transform: translateY(-3px);
822
- }
823
  #cart-button { position: relative; }
824
- #cart-count {
825
- position: absolute;
826
- top: -2px;
827
- right: -2px;
828
- background-color: var(--danger);
829
- color: white;
830
- border-radius: 50%;
831
- padding: 2px 6px;
832
- font-size: 0.7rem;
833
- font-weight: bold;
834
- border: 2px solid var(--accent);
835
- }
836
  .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; }
837
- .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; }
838
- .dark-theme .modal-content { background: #2a2a2a; color: var(--text-light); }
839
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
840
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
841
  .close:hover { color: #666; }
842
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
843
- .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; }
844
- .dark-theme .cart-item { border-bottom-color: #444; }
845
  .cart-item:last-child { border-bottom: none; }
846
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
847
  .cart-item-details { grid-column: 2; }
848
- .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
849
  .cart-item-details .variant-info { font-size: 0.85rem; color: #666; }
850
  .cart-item-price { font-size: 0.9rem; color: #666; }
851
- .dark-theme .cart-item-price { color: #ccc; }
 
852
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; grid-column: 3;}
853
  .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; }
854
- .dark-theme .quantity-btn { background-color: #444; border-color: #555; color: #fff; }
855
  .cart-item-total { font-weight: bold; text-align: right; grid-column: 4; font-size: 1rem; color: var(--bg-medium);}
856
  .cart-item-remove { grid-column: 5; background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
857
  .cart-item-remove:hover { color: var(--danger-hover); }
858
  .quantity-input, .options-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; }
859
- .dark-theme .quantity-input, .dark-theme .options-select { background-color: #333; color: #fff; border-color: #555; }
860
  .quantity-input:focus, .options-select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px rgba(72, 209, 204, 0.2); }
861
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
862
- .dark-theme .cart-summary { border-top-color: #444; }
863
  .cart-summary strong { font-size: 1.2rem; color: var(--bg-medium);}
864
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
865
  .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; }
866
  .product-button i { margin-right: 5px; }
867
  .clear-cart { background-color: #6c757d; }
868
  .clear-cart:hover { background-color: #5a6268; }
869
- .formulate-order-button { background-color: var(--accent); color: var(--bg-dark); }
870
  .formulate-order-button:hover { background-color: var(--accent-hover); }
871
- .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;}
872
  .notification.show { opacity: 1;}
873
- .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; }
874
- .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
875
- .chat-product-card-info { flex-grow: 1; }
876
- .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
877
- .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
878
- .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
879
- .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%; }
880
- .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
881
-
882
  </style>
883
  </head>
884
- <body class="{{ 'dark-theme' if settings.color_scheme == 'dark' else '' }}">
885
  <div class="container">
886
  <div class="top-bar">
887
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="logo">
@@ -1145,12 +1072,6 @@ CATALOG_TEMPLATE = '''
1145
  return;
1146
  }
1147
 
1148
- let price = product.price;
1149
- let variantKey = [color, size].filter(v => v !== 'N/A').join('-');
1150
- if (variantKey && product.variant_prices && product.variant_prices[variantKey]) {
1151
- price = product.variant_prices[variantKey];
1152
- }
1153
-
1154
  const cartItemId = `${product.product_id}-${color}-${size}`;
1155
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
1156
  if (existingItemIndex > -1) {
@@ -1160,19 +1081,42 @@ CATALOG_TEMPLATE = '''
1160
  id: cartItemId,
1161
  product_id: product.product_id,
1162
  name: product.name,
1163
- price: price,
 
 
1164
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
1165
  quantity: quantity,
1166
  color: color,
1167
  size: size
1168
  });
1169
  }
1170
- localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1171
  closeModal('quantityModal');
1172
  updateCartButton();
1173
  showNotification(`${product.name} добавлен в корзину!`);
1174
  }
1175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1176
  function updateCartButton() {
1177
  const cartCountElement = document.getElementById('cart-count');
1178
  const cartButton = document.getElementById('cart-button');
@@ -1198,23 +1142,25 @@ CATALOG_TEMPLATE = '''
1198
  cartTotalElement.textContent = '0.00';
1199
  } else {
1200
  cartContent.innerHTML = cart.map(item => {
1201
- const itemTotal = item.price * item.quantity;
 
1202
  total += itemTotal;
1203
- const photoUrl = item.photo
1204
- ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}`
1205
- : 'https://via.placeholder.com/60x60.png?text=N/A';
1206
 
1207
  let variantInfo = [];
1208
  if (item.color && item.color !== 'N/A') variantInfo.push(`Цвет: ${item.color}`);
1209
  if (item.size && item.size !== 'N/A') variantInfo.push(`Размер: ${item.size}`);
1210
 
 
 
 
1211
  return `
1212
- <div class="cart-item">
1213
  <img src="${photoUrl}" alt="${item.name}">
1214
  <div class="cart-item-details">
1215
  <strong>${item.name}</strong>
1216
  <p class="variant-info">${variantInfo.join(', ')}</p>
1217
- <p class="cart-item-price">${item.price.toFixed(2)} ${currencyCode}</p>
1218
  </div>
1219
  <div class="cart-item-quantity">
1220
  <button class="quantity-btn" onclick="decrementCartItem('${item.id}')">-</button>
@@ -1239,9 +1185,8 @@ CATALOG_TEMPLATE = '''
1239
  const itemIndex = cart.findIndex(item => item.id === itemId);
1240
  if (itemIndex > -1) {
1241
  cart[itemIndex].quantity++;
1242
- localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1243
  openCartModal();
1244
- updateCartButton();
1245
  }
1246
  }
1247
 
@@ -1252,17 +1197,15 @@ CATALOG_TEMPLATE = '''
1252
  if (cart[itemIndex].quantity <= 0) {
1253
  cart.splice(itemIndex, 1);
1254
  }
1255
- localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1256
  openCartModal();
1257
- updateCartButton();
1258
  }
1259
  }
1260
 
1261
  function removeFromCart(itemId) {
1262
  cart = cart.filter(item => item.id !== itemId);
1263
- localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1264
  openCartModal();
1265
- updateCartButton();
1266
  }
1267
 
1268
  function clearCart() {
@@ -1279,10 +1222,17 @@ CATALOG_TEMPLATE = '''
1279
  alert("Корзина пуста! Добавьте товары перед формированием заказа.");
1280
  return;
1281
  }
1282
- const orderData = { cart: cart };
 
 
 
 
 
 
1283
  const formulateButton = document.querySelector('.formulate-order-button');
1284
  if (formulateButton) formulateButton.disabled = true;
1285
  showNotification("Формируем заказ...", 5000);
 
1286
  fetch(`/${envId}/create_order`, {
1287
  method: 'POST',
1288
  headers: { 'Content-Type': 'application/json' },
@@ -1392,31 +1342,31 @@ CHAT_TEMPLATE = '''
1392
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1393
  <style>
1394
  {% if settings.color_scheme == 'forest' %}
1395
- :root { --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; }
1396
  {% elif settings.color_scheme == 'ocean' %}
1397
- :root { --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; }
1398
  {% elif settings.color_scheme == 'sunset' %}
1399
- :root { --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; }
1400
  {% elif settings.color_scheme == 'lavender' %}
1401
- :root { --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; }
1402
  {% elif settings.color_scheme == 'vintage' %}
1403
- :root { --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; }
1404
  {% elif settings.color_scheme == 'dark' %}
1405
- :root { --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; }
1406
  {% elif settings.color_scheme == 'cosmic' %}
1407
- :root { --bg-dark: #2c003e; --bg-medium: #4a0072; --accent: #ff00c1; --accent-hover: #ff5fd8; --text-light: #f3e5f5; --text-dark: #ffffff; --danger: #e91e63; --danger-hover: #f06292; --chat-bg: #1a0024; }
1408
  {% elif settings.color_scheme == 'minty' %}
1409
- :root { --bg-dark: #64B5F6; --bg-medium: #81D4FA; --accent: #B2EBF2; --accent-hover: #E0F7FA; --text-light: #000000; --text-dark: #000000; --danger: #ef5350; --danger-hover: #e57373; --chat-bg: #f1f8e9; }
1410
  {% elif settings.color_scheme == 'mocha' %}
1411
- :root { --bg-dark: #3e2723; --bg-medium: #5d4037; --accent: #a1887f; --accent-hover: #bcaaa4; --text-light: #efebe9; --text-dark: #ffffff; --danger: #d32f2f; --danger-hover: #e57373; --chat-bg: #d7ccc8; }
1412
  {% elif settings.color_scheme == 'crimson' %}
1413
- :root { --bg-dark: #121212; --bg-medium: #b71c1c; --accent: #f44336; --accent-hover: #ef5350; --text-light: #ffffff; --text-dark: #ffffff; --danger: #ff5252; --danger-hover: #ff8a80; --chat-bg: #212121; }
1414
  {% elif settings.color_scheme == 'solar' %}
1415
- :root { --bg-dark: #ff6f00; --bg-medium: #ff8f00; --accent: #ffca28; --accent-hover: #ffd54f; --text-light: #212121; --text-dark: #212121; --danger: #d32f2f; --danger-hover: #e57373; --chat-bg: #fff8e1; }
1416
  {% elif settings.color_scheme == 'cyberpunk' %}
1417
- :root { --bg-dark: #000000; --bg-medium: #0d0221; --accent: #00f0ff; --accent-hover: #81f5ff; --text-light: #ffffff; --text-dark: #ffffff; --danger: #f50057; --danger-hover: #ff4081; --chat-bg: #0a0116; }
1418
  {% else %}
1419
- :root { --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; }
1420
  {% endif %}
1421
 
1422
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -1430,154 +1380,71 @@ CHAT_TEMPLATE = '''
1430
  height: 100%;
1431
  overflow: hidden;
1432
  }
1433
- .chat-container {
1434
- display: flex;
1435
- flex-direction: column;
1436
- height: 100%;
1437
- width: 100%;
1438
- max-width: 800px;
1439
- margin: 0 auto;
1440
- background: #fff;
1441
- box-shadow: 0 0 20px rgba(0,0,0,0.05);
1442
- }
1443
- body.dark-theme .chat-container { background: var(--chat-bg); color: var(--text-light); }
1444
- .chat-header {
1445
- display: flex;
1446
- align-items: center;
1447
- padding: 10px 15px;
1448
- background: var(--bg-dark);
1449
- color: var(--text-light);
1450
- flex-shrink: 0;
1451
- }
1452
  .chat-header a { color: var(--text-light); font-size: 1.2rem; text-decoration: none; }
1453
  .chat-header .logo { width: 40px; height: 40px; border-radius: 50%; margin: 0 15px; border: 2px solid var(--accent); }
1454
  .chat-header h1 { font-size: 1.2rem; font-weight: 600; }
1455
- #chat-messages {
1456
- flex-grow: 1;
1457
- overflow-y: auto;
1458
- padding: 20px 15px;
1459
- display: flex;
1460
- flex-direction: column;
1461
- gap: 12px;
1462
- }
1463
- .chat-message {
1464
- display: flex;
1465
- flex-direction: column;
1466
- max-width: 85%;
1467
- animation: message-appear 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
1468
- }
1469
- @keyframes message-appear {
1470
- from { opacity: 0; transform: translateY(15px); }
1471
- to { opacity: 1; transform: translateY(0); }
1472
- }
1473
- .message-bubble {
1474
- padding: 10px 15px;
1475
- border-radius: 18px;
1476
- line-height: 1.5;
1477
- word-wrap: break-word;
1478
- }
1479
  .chat-message.user { align-self: flex-end; }
1480
- .chat-message.user .message-bubble {
1481
- background-color: var(--bg-medium);
1482
- color: white;
1483
- border-bottom-right-radius: 4px;
1484
- }
1485
  .chat-message.ai { align-self: flex-start; }
1486
- .chat-message.ai .message-bubble {
1487
- background-color: #e6e6e6;
1488
- color: var(--text-dark);
1489
- border-bottom-left-radius: 4px;
1490
- }
1491
- body.dark-theme .chat-message.ai .message-bubble { background-color: #333; color: var(--text-light); }
1492
- .chat-input-container {
1493
- padding: 15px;
1494
- background: #fff;
1495
- border-top: 1px solid #ddd;
1496
- display: flex;
1497
- gap: 10px;
1498
- align-items: center;
1499
- flex-shrink: 0;
1500
- }
1501
- body.dark-theme .chat-input-container { background: var(--bg-dark); border-top: 1px solid #444;}
1502
- #chat-input {
1503
- flex-grow: 1;
1504
- padding: 12px 18px;
1505
- border: 1px solid #e0e0e0;
1506
- border-radius: 24px;
1507
- font-size: 1rem;
1508
- outline: none;
1509
- transition: border-color 0.3s, box-shadow 0.3s;
1510
- }
1511
- body.dark-theme #chat-input { background-color: #333; color: var(--text-light); border-color: #555; }
1512
- #chat-input:focus {
1513
- border-color: var(--bg-medium);
1514
- box-shadow: 0 0 0 3px rgba(19, 93, 102, 0.15);
1515
- }
1516
- #chat-send-button {
1517
- background-color: var(--bg-medium);
1518
- color: white;
1519
- border: none;
1520
- border-radius: 50%;
1521
- width: 48px;
1522
- height: 48px;
1523
- display: flex;
1524
- justify-content: center;
1525
- align-items: center;
1526
- cursor: pointer;
1527
- transition: background-color 0.3s, transform 0.2s;
1528
- flex-shrink: 0;
1529
- font-size: 1.2rem;
1530
- }
1531
  #chat-send-button:hover { background-color: var(--bg-dark); }
1532
  #chat-send-button:active { transform: scale(0.9); }
1533
  #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
1534
  .floating-buttons-container { position: fixed; bottom: 25px; right: 25px; z-index: 1000; }
1535
- .floating-button { background-color: var(--accent); color: var(--bg-dark); border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
1536
  .floating-button:hover { background-color: var(--accent-hover); transform: translateY(-3px); }
1537
  #cart-button { position: relative; }
1538
  #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); }
1539
  .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; }
1540
- .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; }
1541
- body.dark-theme .modal-content { background-color: #2a2a2a; color: var(--text-light); }
1542
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
1543
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
1544
  .close:hover { color: #666; }
1545
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
1546
- .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; }
1547
- body.dark-theme .cart-item { border-bottom-color: #444; }
1548
  .cart-item:last-child { border-bottom: none; }
1549
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
1550
- .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; color: var(--text-dark);}
1551
- body.dark-theme .cart-item-details strong { color: var(--text-light); }
1552
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; }
1553
  .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; }
1554
- body.dark-theme .quantity-btn { background-color: #444; border-color: #555; color: #fff; }
1555
  .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--bg-medium);}
1556
  .cart-item-remove { background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; }
1557
  .quantity-input, .options-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; }
1558
- body.dark-theme .quantity-input, body.dark-theme .options-select { background-color: #333; color: #fff; border-color: #555; }
1559
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
1560
- body.dark-theme .cart-summary { border-top-color: #444; }
1561
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; }
1562
- .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; }
1563
- .clear-cart { background-color: #6c757d; }
1564
- .formulate-order-button { background-color: var(--accent); color: var(--bg-dark); }
1565
- .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;}
1566
  .notification.show { opacity: 1;}
1567
- .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; }
1568
- body.dark-theme .chat-product-card { background-color: #333; border-color: #555; }
1569
  .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
1570
  .chat-product-card-info { flex-grow: 1; }
1571
- .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--text-dark); margin-bottom: 2px; }
1572
- body.dark-theme .chat-product-card-info strong { color: var(--text-light); }
1573
  .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
1574
  .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
1575
- .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%; }
1576
- .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
1577
- body.dark-theme .chat-product-link, body.dark-theme .chat-add-to-cart { background-color: #444; color: var(--accent); }
1578
  </style>
1579
  </head>
1580
- <body class="{{ 'dark-theme' if settings.color_scheme == 'dark' else '' }}">
1581
  <div class="chat-container">
1582
  <div class="chat-header">
1583
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
@@ -1714,24 +1581,43 @@ CHAT_TEMPLATE = '''
1714
  }
1715
  const product = getProductById(selectedProductId);
1716
 
1717
- let price = product.price;
1718
- let variantKey = [color, size].filter(v => v !== 'N/A').join('-');
1719
- if (variantKey && product.variant_prices && product.variant_prices[variantKey]) {
1720
- price = product.variant_prices[variantKey];
1721
- }
1722
-
1723
  const cartItemId = `${product.product_id}-${color}-${size}`;
1724
- const existingItem = cart.find(item => item.id === cartItemId);
1725
- if (existingItem) {
1726
- existingItem.quantity += quantity;
1727
  } else {
1728
- cart.push({ id: cartItemId, product_id: product.product_id, name: product.name, price: price, photo: product.photos ? product.photos[0] : null, quantity, color, size });
 
 
 
 
 
 
 
 
 
 
 
1729
  }
1730
  localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1731
  closeModal('quantityModal');
1732
  updateCartButton();
1733
  showNotification(`${product.name} добавлен в корзину!`);
1734
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1735
  function updateCartButton() {
1736
  const cartCountEl = document.getElementById('cart-count');
1737
  const cartButton = document.getElementById('cart-button');
@@ -1754,14 +1640,16 @@ CHAT_TEMPLATE = '''
1754
  cartTotalEl.textContent = '0.00';
1755
  } else {
1756
  cartContent.innerHTML = cart.map(item => {
1757
- const itemTotal = item.price * item.quantity;
 
1758
  total += itemTotal;
1759
  let variantInfo = [];
1760
  if (item.color && item.color !== 'N/A') variantInfo.push(`Цвет: ${item.color}`);
1761
  if (item.size && item.size !== 'N/A') variantInfo.push(`Размер: ${item.size}`);
 
1762
  return `<div class="cart-item">
1763
  <img src="${item.photo ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}` : ''}" alt="${item.name}">
1764
- <div><strong>${item.name}</strong><p>${variantInfo.join(', ')}</p><p>${item.price.toFixed(2)} ${currencyCode}</p></div>
1765
  <div class="cart-item-quantity"><button class="quantity-btn" onclick="decrementCartItem('${item.id}')">-</button><span>${item.quantity}</span><button class="quantity-btn" onclick="incrementCartItem('${item.id}')">+</button></div>
1766
  <span class="cart-item-total">${itemTotal.toFixed(2)}</span>
1767
  <button class="cart-item-remove" onclick="removeFromCart('${item.id}')"><i class="fas fa-trash-alt"></i></button>
@@ -1804,7 +1692,8 @@ CHAT_TEMPLATE = '''
1804
  }
1805
  function formulateOrder() {
1806
  if (cart.length === 0) return;
1807
- fetch(`/${envId}/create_order`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cart }) })
 
1808
  .then(res => res.json())
1809
  .then(data => {
1810
  if (data.order_id) {
@@ -1834,7 +1723,7 @@ CHAT_TEMPLATE = '''
1834
  const messageElement = document.createElement('div');
1835
  messageElement.className = `chat-message ${role}`;
1836
 
1837
- const productRegex = /\[ID_ТОВАРА:\\s*([a-fA-F0-9]+)\\s*Название:\\s*([^\]]+)\]/g;
1838
  let lastIndex = 0;
1839
  const contentFragment = document.createDocumentFragment();
1840
  let match;
@@ -1843,7 +1732,7 @@ CHAT_TEMPLATE = '''
1843
  if (match.index > lastIndex) {
1844
  const textNode = document.createElement('div');
1845
  textNode.className = 'message-bubble';
1846
- textNode.innerHTML = text.substring(lastIndex, match.index).replace(/\\n/g, '<br>');
1847
  messageElement.appendChild(textNode);
1848
  }
1849
  const productId = match[1];
@@ -1869,7 +1758,7 @@ CHAT_TEMPLATE = '''
1869
  if (lastIndex < text.length) {
1870
  const textNode = document.createElement('div');
1871
  textNode.className = 'message-bubble';
1872
- textNode.innerHTML = text.substring(lastIndex).replace(/\\n/g, '<br>');
1873
  messageElement.appendChild(textNode);
1874
  }
1875
 
@@ -1982,7 +1871,12 @@ PRODUCT_DETAIL_TEMPLATE = '''
1982
  </div>
1983
 
1984
  <div style="text-align:center; margin-top:20px; padding: 0 10px;">
1985
- <p style="font-size: 1.5rem; font-weight: bold; color: #135D66; margin-bottom: 15px;"><strong>Цена:</strong> <span id="variantPrice">{{ "%.0f"|format(product.price) }} {{ currency_code }}</span></p>
 
 
 
 
 
1986
  <button class="product-button formulate-order-button" style="padding: 12px 30px; width: 100%; max-width: 300px;" onclick="closeModal('productModal'); openQuantityModalById('{{ product.get('product_id', '') }}')">
1987
  <i class="fas fa-cart-plus"></i> В корзину
1988
  </button>
@@ -2044,6 +1938,17 @@ ORDER_TEMPLATE = '''
2044
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--bg-medium); text-decoration: none; font-size: 0.9rem; }
2045
  .catalog-link:hover { text-decoration: underline; }
2046
  .not-found { text-align: center; color: #dc3545; font-size: 1.2rem; padding: 40px 0;}
 
 
 
 
 
 
 
 
 
 
 
2047
  </style>
2048
  </head>
2049
  <body>
@@ -2086,12 +1991,13 @@ ORDER_TEMPLATE = '''
2086
  let variantInfo = [];
2087
  if (item.color && item.color !== 'N/A') variantInfo.push(`Цвет: ${item.color}`);
2088
  if (item.size && item.size !== 'N/A') variantInfo.push(`Размер: ${item.size}`);
 
2089
 
2090
  return `
2091
  <div class="order-item">
2092
  <img src="${item.photo_url}" alt="${item.name}">
2093
  <div class="item-details">
2094
- <strong>${item.name}</strong>
2095
  <span>${variantInfo.join(', ')}</span>
2096
  <span>${item.price.toFixed(2)} {{ currency_code }}</span>
2097
  </div>
@@ -2154,8 +2060,9 @@ ORDER_TEMPLATE = '''
2154
  if (item.color && item.color !== 'N/A') variantInfo.push(item.color);
2155
  if (item.size && item.size !== 'N/A') variantInfo.push(item.size);
2156
  let variantText = variantInfo.length > 0 ? ` (${variantInfo.join(', ')})` : '';
 
2157
 
2158
- message += `*${item.name}*${variantText}%0A`;
2159
  message += ` - Количество: ${item.quantity}%0A`;
2160
  message += ` - Цена: ${item.price.toFixed(2)} {{ currency_code }}%0A`;
2161
  });
@@ -2277,6 +2184,7 @@ ADMIN_TEMPLATE = '''
2277
  .variant-price-item { display: flex; align-items: center; gap: 8px; font-size: 0.9em; }
2278
  .variant-price-item label { margin-top: 0; white-space: nowrap; }
2279
  .variant-price-item input { margin-top: 0; }
 
2280
  </style>
2281
  </head>
2282
  <body>
@@ -2331,6 +2239,13 @@ ADMIN_TEMPLATE = '''
2331
  <label for="organization_name">Название организации:</label>
2332
  <input type="text" id="organization_name" name="organization_name" value="{{ settings.organization_name }}">
2333
 
 
 
 
 
 
 
 
2334
  <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
2335
  <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.whatsapp_number }}" placeholder="+996XXXXXXXXX">
2336
 
@@ -2455,8 +2370,22 @@ ADMIN_TEMPLATE = '''
2455
  <input type="hidden" name="action" value="add_product">
2456
  <label for="add_name">Название товара *:</label>
2457
  <input type="text" id="add_name" name="name" required>
2458
- <label for="add_price">Базовая Цена ({{ currency_code }}) *:</label>
 
 
2459
  <input type="number" id="add_price" name="price" step="0.01" min="0" required>
 
 
 
 
 
 
 
 
 
 
 
 
2460
  <label for="add_photos">Фотографии (до 10 шт.):</label>
2461
  <input type="file" id="add_photos" name="photos" accept="image/*" multiple>
2462
  <label for="add_description">Описание:</label>
@@ -2541,13 +2470,17 @@ ADMIN_TEMPLATE = '''
2541
  {% endif %}
2542
  </h3>
2543
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
2544
- <p><strong>Цена:</strong> {{ "%.2f"|format(product.price) }} {{ currency_code }}</p>
 
 
 
 
 
2545
  <p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>
2546
  {% set colors = product.get('colors', []) %}
2547
  {% set sizes = product.get('sizes', []) %}
2548
  <p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
2549
  <p><strong>Размеры/Объем:</strong> {{ sizes|select('ne', '')|join(', ') if sizes|select('ne', '')|list|length > 0 else 'Нет' }}</p>
2550
-
2551
  {% if product.get('photos') and product['photos']|length > 1 %}
2552
  <p style="font-size: 0.8rem; color: #999;">(Всего фото: {{ product['photos']|length }})</p>
2553
  {% endif %}
@@ -2570,8 +2503,22 @@ ADMIN_TEMPLATE = '''
2570
  <input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}">
2571
  <label>Название *:</label>
2572
  <input type="text" name="name" value="{{ product['name'] }}" required>
2573
- <label>Цена ({{ currency_code }}) *:</label>
 
 
2574
  <input type="number" name="price" step="0.01" min="0" value="{{ product['price'] }}" required>
 
 
 
 
 
 
 
 
 
 
 
 
2575
  <label>Заменить фотографии (выберите новые файлы, до 10 шт.):</label>
2576
  <input type="file" id="edit_photos_{{ loop.index0 }}" name="photos" accept="image/*" multiple>
2577
  {% if product.get('photos') %}
@@ -2915,16 +2862,22 @@ def admhosto():
2915
 
2916
  environments_data.append({
2917
  "id": env_id,
 
2918
  "chat_active": is_active,
2919
  "expires_soon": expires_soon,
2920
  "expires_date": expires_date_str
2921
  })
2922
 
2923
  environments_data.sort(key=lambda x: x['id'])
2924
- return render_template_string(ADMHOSTO_TEMPLATE, environments=environments_data)
2925
 
2926
  @app.route('/admhosto/create', methods=['POST'])
2927
  def create_environment():
 
 
 
 
 
2928
  all_data = load_data()
2929
  while True:
2930
  new_id = ''.join(random.choices(string.digits, k=6))
@@ -2942,19 +2895,20 @@ def create_environment():
2942
  "contact": "Наш магазин находится по адресу: ... Связаться с нами можно по телефону ..."
2943
  },
2944
  'settings': {
2945
- "organization_name": "Gippo312",
2946
  "whatsapp_number": "+996701202013",
2947
  "currency_code": "KGS",
2948
  "chat_name": "EVA",
2949
  "chat_avatar": None,
2950
  "color_scheme": "default",
2951
  "chat_activated": False,
2952
- "chat_activation_expires": None
 
2953
  },
2954
  'chats': {}
2955
  }
2956
  save_data(all_data)
2957
- flash(f'Новая среда с ID {new_id} успешно создана.', 'success')
2958
  return redirect(url_for('admhosto'))
2959
 
2960
  @app.route('/admhosto/delete/<env_id>', methods=['POST'])
@@ -3104,6 +3058,7 @@ def create_order(env_id):
3104
  "color": item.get('color', 'N/A'),
3105
  "size": item.get('size', 'N/A'),
3106
  "photo": item.get('photo'),
 
3107
  "photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photo']}" if item.get('photo') else "https://via.placeholder.com/60x60.png?text=N/A"
3108
  })
3109
  total_price += price * quantity
@@ -3204,6 +3159,7 @@ def admin(env_id):
3204
 
3205
  elif action == 'update_settings':
3206
  settings['organization_name'] = request.form.get('organization_name', 'Gippo312').strip()
 
3207
  settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
3208
  settings['currency_code'] = request.form.get('currency_code', 'KGS')
3209
  settings['chat_name'] = request.form.get('chat_name', 'EVA').strip()
@@ -3280,6 +3236,27 @@ def admin(env_id):
3280
  flash("Неверный формат цены.", 'error')
3281
  return redirect(url_for('admin', env_id=env_id))
3282
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3283
  variant_prices = {}
3284
  for key, value in request.form.items():
3285
  if key.startswith('variant_price_') and value:
@@ -3420,6 +3397,7 @@ def admin(env_id):
3420
  chat_avatar_url=chat_avatar_url,
3421
  currencies=CURRENCIES,
3422
  color_schemes=COLOR_SCHEMES,
 
3423
  env_id=env_id,
3424
  chat_status=chat_status
3425
  )
 
64
  'cyberpunk': 'Киберпанк неон'
65
  }
66
 
67
+ BUSINESS_TYPES = {
68
+ 'retail': 'Розница',
69
+ 'wholesale': 'Опт',
70
+ 'both': 'Опт и Розница'
71
+ }
72
+
73
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
74
 
75
  def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY):
 
219
  "chat_avatar": None,
220
  "color_scheme": "default",
221
  "chat_activated": False,
222
+ "chat_activation_expires": None,
223
+ "business_type": "retail"
224
  }
225
 
226
  env_data = all_data.get(env_id, {})
 
255
  if 'variant_prices' not in product:
256
  product['variant_prices'] = {}
257
  products_changed = True
258
+ if 'wholesale_price' not in product:
259
+ product['wholesale_price'] = None
260
+ products_changed = True
261
+ if 'min_wholesale_quantity' not in product:
262
+ product['min_wholesale_quantity'] = None
263
+ products_changed = True
264
 
265
  if products_changed or settings_changed:
266
  save_env_data(env_id, env_data)
 
354
  currency_code = settings.get('currency_code', 'KGS')
355
  chat_name = settings.get('chat_name', 'EVA')
356
  org_name = settings.get('organization_name', 'Gippo312')
357
+ business_type = settings.get('business_type', 'retail')
358
 
359
  product_info_list = []
360
  for p in products:
361
  if p.get('in_stock', True):
362
  price_display = f"{p.get('price', 0):.2f}".replace('.00', '')
363
+ wholesale_price_info = ""
364
+ if business_type in ['wholesale', 'both'] and p.get('wholesale_price') and p.get('min_wholesale_quantity'):
365
+ wholesale_price_display = f"{p['wholesale_price']:.2f}".replace('.00', '')
366
+ wholesale_price_info = f", Оптовая цена: {wholesale_price_display} {currency_code} от {p['min_wholesale_quantity']} шт."
367
+
368
  colors_str = f"Цвета: {', '.join(p.get('colors', []))}" if p.get('colors') else ""
369
  sizes_str = f"Размеры: {', '.join(p.get('sizes', []))}" if p.get('sizes') else ""
370
 
 
378
  if variant_prices_list:
379
  variant_prices_str = f", Особые цены: [{'; '.join(variant_prices_list)}]"
380
 
381
+ retail_price_label = "Розничная цена" if wholesale_price_info else "Базовая цена"
382
+
383
  product_info_list.append(
384
  f"- [ID_ТОВАРА: {p.get('product_id', 'N/A')} Название: {p.get('name', 'Без названия')}], "
385
  f"Категория: {p.get('category', 'Без категории')}, "
386
+ f"{retail_price_label}: {price_display} {currency_code}"
387
+ f"{wholesale_price_info}{options_str}{variant_prices_str}, "
388
  f"Описание: {p.get('description', '')[:100]}..."
389
  )
390
  product_list_str = "\n".join(product_info_list) if product_info_list else "В данный момент нет товаров в наличии."
 
403
  if organization_info.get("contact"):
404
  org_info_str += f"Контактная информация: {organization_info['contact']}\n"
405
 
406
+ prompts = {
407
+ "retail": (
408
+ f"Ты — первоклассный виртуальный консультант-продажник по имени {chat_name} для розничного магазина {org_name}. Твоя главная цель — не просто отвечать на вопросы, а продавать, используя все свои навыки. "
409
+ "Говори на любом языке, на котором к тебе обращается клиент. Будь энергичным, убедительным и проактивным. "
410
+ "Твоя задача — помогать пользователям находить товары, мастерски отвечать на вопросы о них, предлагать лучшие варианты, создавать ценность и закрывать сделку. "
411
+ "Всегда будь вежлив, но настойчив. Твоя речь должна быть живой, с использованием эмодзи, чтобы располагать к себе клиента. "
412
+ "Никогда не выдумывай товары, категории или характеристики, которых нет в предоставленных списках. "
413
+ "Когда ты предлагаешь товар, всегда указывай его название и ID, используя *точный формат*: [ID_ТОВАРА: <product_id> Название: <product_name>]. Это *критически важно* для клиента. "
414
+ "Если пользователь спрашивает цену на конкретный вариант (цвет или размер), найди ее в 'Особые цены'. Если там нет, используй 'Базовая цена'. "
415
+ "Активно предлагай сопутствующие товары или более дорогие аналоги (апсейл). Например: 'Отличный выбор! К этому телефону идеально подойдут наши новые беспроводные наушники. Хотите взглянуть?'. "
416
+ "Создавай ощущение срочности: 'Эта модель сейчас очень популярна, осталось всего несколько штук!'. "
417
+ "Работай с возражениями: если клиент говорит 'дорого', расскажи о качестве, гарантии и уникальных особенностях товара."
418
+ ),
419
+ "wholesale": (
420
+ f"Ты — профессиональный менеджер по оптовым продажам по имени {chat_name} компании {org_name}. Твоя цель — заключать крупные сделки и выстраивать долгосрочные отношения с оптовыми клиентами. "
421
+ "Общайся с клиентами на их языке. Будь деловым, компетентным и убедительным. "
422
+ "Твоя задача — предоставлять информацию об оптовых ценах, минимальных партиях заказа (МОЗ), условиях доставки и оплаты. "
423
+ "Всегда оперируй только теми данными о товарах, которые есть в списке. Не придумывай ничего. "
424
+ "Когда предлагаешь товар, всегда указывай его название и ID в формате: [ID_ТОВАРА: <product_id> Название: <product_name>]. "
425
+ "Четко указывай оптовую цену и минимальное количество для заказа. Например: 'Оптовая цена на [Название товара] составляет X {currency_code} при заказе от Y штук'. "
426
+ "Если клиент спрашивает о рознице, вежливо сообщи, что ты специализируешься на оптовых продажах, но можешь предоставить розничную цену для сравнения, если она указана. "
427
+ "Будь готов обсуждать скидки на крупные объемы и условия сотрудничества. Твоя цель — крупный заказ и довольный партнер."
428
+ ),
429
+ "both": (
430
+ f"Ты — универсальный менеджер по продажам по имени {chat_name} в магазине {org_name}, который работает как с розничными, так и с оптовыми покупателями. Твоя главная задача — определить тип клиента и предложить ему лучшие условия. "
431
+ "Говори с клиентами на их языке, будь дружелюбен с розничными покупателями и деловым с оптовиками. Используй эмодзи для создания приятной атмосферы. "
432
+ "В начале диалога постарайся выяснить, интересует ли клиента розничная покупка или оптовая партия. Например: 'Вас интересует покупка для себя или для бизнеса?'. "
433
+ "Для розничных клиентов: работай как консультант-продажник, предлагай сопутствующие товары, рассказывай о преимуществах. "
434
+ "Для оптовых клиентов: четко сообщай оптовую цену и минимальное количество для заказа. Например: 'Розничная цена — X {currency_code}, но при покупке от Y штук цена будет всего Z {currency_code} за единицу!'. "
435
+ "Всегда используй *только* данные из предоставленного списка товаров. Не выдумывай цены или условия. "
436
+ "При упоминании товара всегда используй формат: [ID_ТОВАРА: <product_id> Название: <product_name>]. Это очень важно. "
437
+ "Твоя сила — в гибкости. Умей переключаться между стилями общения и закрывай как можно больше сделок, как розничных, так и оптовых."
438
+ )
439
+ }
440
 
441
+ base_prompt = prompts.get(business_type, prompts['retail'])
442
+
443
  system_instruction_content = (
444
+ f"{base_prompt}\n\n"
 
 
 
 
 
 
 
 
 
445
  f"Список доступных категорий: {category_list_str}.\n\n"
446
  f"Список доступных товаров в магазине (используй эту информацию для ответов):\n"
447
  f"{product_list_str}"
 
552
  .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); }
553
  h1 { font-weight: 600; color: var(--bg-medium); margin-bottom: 25px; text-align: center; }
554
  .section { margin-bottom: 30px; }
555
+ .add-env-form { display: flex; flex-direction: column; align-items: center; gap: 15px; margin-bottom: 20px; }
556
+ .add-env-form select { padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-family: 'Montserrat', sans-serif; font-size: 1rem;}
557
+ #search-env { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; font-size: 1rem; font-family: 'Montserrat', sans-serif;}
 
 
 
 
 
 
 
558
  .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; }
559
  .button:hover { background-color: var(--accent-hover); }
560
  .button:disabled { background-color: #ccc; cursor: not-allowed; }
561
  .env-list { list-style: none; padding: 0; }
562
  .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; }
563
+ .env-details { display: flex; flex-direction: column; gap: 4px;}
564
  .env-id { font-weight: 600; color: var(--bg-medium); font-size: 1.2rem; }
565
+ .env-sub-details { display: flex; flex-wrap: wrap; gap: 10px; align-items: center;}
566
  .env-status { font-size: 0.85rem; color: #666; }
567
  .env-status .expires-soon { color: var(--danger); font-weight: bold; }
568
+ .env-type { font-size: 0.8rem; background-color: #e0e0e0; padding: 2px 8px; border-radius: 10px; }
569
  .env-actions { display: flex; gap: 10px; flex-wrap: wrap; }
570
  .delete-button { background-color: var(--danger); color: white; }
571
  .activate-button { background-color: #66BB6A; color: white; }
572
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; text-align: center; }
573
  .message.success { background-color: #d4edda; color: #155724; }
574
  .message.error { background-color: #f8d7da; color: #721c24; }
575
+ @media (max-width: 768px) {
576
+ .env-item { grid-template-columns: 1fr; text-align: center; }
577
+ .env-details, .env-actions { justify-content: center; }
578
+ .add-env-form { flex-direction: column; }
579
+ .env-sub-details { justify-content: center; }
580
+ }
581
  </style>
582
  </head>
583
  <body>
 
594
 
595
  <div class="section">
596
  <form method="POST" action="{{ url_for('create_environment') }}" class="add-env-form">
597
+ <select name="business_type" required>
598
+ <option value="" disabled selected>Выберите тип бизнеса</option>
599
+ {% for key, name in business_types.items() %}
600
+ <option value="{{ key }}">{{ name }}</option>
601
+ {% endfor %}
602
+ </select>
603
  <button type="submit" class="button"><i class="fas fa-plus-circle"></i> Создать новую среду</button>
604
  </form>
605
  </div>
 
616
  <li class="env-item">
617
  <div class="env-details">
618
  <span class="env-id">{{ env.id }}</span>
619
+ <div class="env-sub-details">
620
+ <span class="env-type">{{ business_types.get(env.business_type, 'N/A') }}</span>
621
+ <div class="env-status">
622
+ {% if env.chat_active %}
623
+ Активирован до: <span class="{{ 'expires-soon' if env.expires_soon else '' }}">{{ env.expires_date }}</span>
624
+ {% else %}
625
+ Чат не активирован
626
+ {% endif %}
627
+ </div>
628
  </div>
629
  </div>
630
  <div class="env-actions">
631
  <a href="{{ url_for('admin', env_id=env.id) }}" class="button" target="_blank"><i class="fas fa-tools"></i> Админ</a>
632
  <a href="{{ url_for('catalog', env_id=env.id) }}" class="button" target="_blank"><i class="fas fa-store"></i> Каталог</a>
633
+ <form method="POST" action="{{ url_for('activate_chat', env_id=env.id) }}" style="display:inline-flex; gap: 5px; align-items:center;">
634
  <select name="period" style="padding: 5px; border-radius: 4px; border: 1px solid #ccc;">
635
  <option value="month">1 месяц</option>
636
  <option value="half_year">6 месяцев</option>
 
682
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
683
  <style>
684
  {% if settings.color_scheme == 'forest' %}
685
+ :root { --bg-dark: #2F4F4F; --bg-medium: #556B2F; --accent: #90EE90; --accent-hover: #72b372; --text-light: #F5F5DC; --text-dark: #ffffff; --danger: #CD5C5C; --danger-hover: #F08080; --text-on-accent: #2F4F4F; }
686
  {% elif settings.color_scheme == 'ocean' %}
687
+ :root { --bg-dark: #000080; --bg-medium: #1E90FF; --accent: #87CEEB; --accent-hover: #65b2d6; --text-light: #F0F8FF; --text-dark: #ffffff; --danger: #FF6347; --danger-hover: #FF4500; --text-on-accent: #000080; }
688
  {% elif settings.color_scheme == 'sunset' %}
689
+ :root { --bg-dark: #8B4513; --bg-medium: #D2691E; --accent: #FFA500; --accent-hover: #d18700; --text-light: #FFF8DC; --text-dark: #ffffff; --danger: #DC143C; --danger-hover: #FF0000; --text-on-accent: #8B4513; }
690
  {% elif settings.color_scheme == 'lavender' %}
691
+ :root { --bg-dark: #483D8B; --bg-medium: #9370DB; --accent: #E6E6FA; --accent-hover: #c4c4e0; --text-light: #F0F8FF; --text-dark: #ffffff; --danger: #DB7093; --danger-hover: #FFC0CB; --text-on-accent: #483D8B; }
692
  {% elif settings.color_scheme == 'vintage' %}
693
+ :root { --bg-dark: #5D4037; --bg-medium: #A1887F; --accent: #D7CCC8; --accent-hover: #b7a9a4; --text-light: #F5F5F5; --text-dark: #ffffff; --danger: #BF360C; --danger-hover: #F4511E; --text-on-accent: #3E2723; }
694
  {% elif settings.color_scheme == 'dark' %}
695
+ :root { --bg-dark: #121212; --bg-medium: #1E1E1E; --accent: #BB86FC; --accent-hover: #A764FC; --text-light: #E1E1E1; --text-dark: #FFFFFF; --danger: #CF6679; --danger-hover: #D98899; --text-on-accent: #121212; }
696
  {% elif settings.color_scheme == 'cosmic' %}
697
+ :root { --bg-dark: #2c003e; --bg-medium: #4a0072; --accent: #f929ff; --accent-hover: #ff5fd8; --text-light: #f3e5f5; --text-dark: #ffffff; --danger: #e91e63; --danger-hover: #f06292; --text-on-accent: #2c003e; }
698
  {% elif settings.color_scheme == 'minty' %}
699
+ :root { --bg-dark: #004D40; --bg-medium: #00796B; --accent: #4DB6AC; --accent-hover: #80CBC4; --text-light: #E0F2F1; --text-dark: #ffffff; --danger: #ef5350; --danger-hover: #e57373; --text-on-accent: #E0F2F1; }
700
  {% elif settings.color_scheme == 'mocha' %}
701
+ :root { --bg-dark: #3e2723; --bg-medium: #5d4037; --accent: #a1887f; --accent-hover: #bcaaa4; --text-light: #efebe9; --text-dark: #ffffff; --danger: #d32f2f; --danger-hover: #e57373; --text-on-accent: #3e2723; }
702
  {% elif settings.color_scheme == 'crimson' %}
703
+ :root { --bg-dark: #121212; --bg-medium: #b71c1c; --accent: #ff5252; --accent-hover: #ff8a80; --text-light: #ffffff; --text-dark: #ffffff; --danger: #c62828; --danger-hover: #e53935; --text-on-accent: #ffffff; }
704
  {% elif settings.color_scheme == 'solar' %}
705
+ :root { --bg-dark: #212121; --bg-medium: #424242; --accent: #ffca28; --accent-hover: #ffd54f; --text-light: #ffffff; --text-dark: #ffffff; --danger: #d32f2f; --danger-hover: #e57373; --text-on-accent: #212121; }
706
  {% elif settings.color_scheme == 'cyberpunk' %}
707
+ :root { --bg-dark: #000000; --bg-medium: #0d0221; --accent: #00f0ff; --accent-hover: #81f5ff; --text-light: #ffffff; --text-dark: #ffffff; --danger: #f50057; --danger-hover: #ff4081; --text-on-accent: #0d0221; }
708
  {% else %}
709
+ :root { --bg-dark: #003C43; --bg-medium: #135D66; --accent: #48D1CC; --accent-hover: #77E4D8; --text-light: #E3FEF7; --text-dark: #ffffff; --danger: #E57373; --danger-hover: #EF5350; --text-on-accent: #003C43; }
710
  {% endif %}
711
 
712
  * { margin: 0; padding: 0; box-sizing: border-box; }
 
714
  body {
715
  font-family: 'Montserrat', sans-serif;
716
  background-color: var(--bg-dark);
717
+ color: var(--text-dark);
718
  line-height: 1.6;
719
  }
720
  .container {
 
733
  z-index: 999;
734
  border-bottom: 1px solid var(--bg-medium);
735
  }
736
+ .logo { flex-shrink: 0; }
737
+ .logo img { width: 45px; height: 45px; border-radius: 50%; border: 2px solid var(--accent); }
738
+ .search-wrapper { flex-grow: 1; position: relative; }
739
+ #search-input { width: 100%; padding: 12px 20px 12px 45px; font-size: 1rem; border: none; border-radius: 25px; outline: none; background-color: var(--bg-medium); color: var(--text-dark); transition: all 0.3s ease; }
740
+ #search-input::placeholder { color: rgba(255, 255, 255, 0.6); }
741
+ .search-wrapper .fa-search { position: absolute; top: 50%; left: 18px; transform: translateY(-50%); color: rgba(255, 255, 255, 0.6); font-size: 1rem; }
742
+ .category-section { margin-top: 20px; }
743
+ .category-header { display: flex; justify-content: space-between; align-items: center; padding: 0 20px; margin-bottom: 15px; }
744
+ .category-header h2 { font-size: 1.5rem; font-weight: 600; color: var(--text-light, #E3FEF7); }
745
+ .category-header .view-all-arrow { font-size: 1.8rem; color: var(--accent); text-decoration: none; font-weight: 300; }
746
+ .product-carousel { display: flex; overflow-x: auto; gap: 16px; padding: 10px 20px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
  .product-carousel::-webkit-scrollbar { display: none; }
748
+ .product-card { width: 170px; height: 170px; background: white; border-radius: 16px; flex-shrink: 0; overflow: hidden; cursor: pointer; transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); position: relative; }
749
+ .product-card:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
  .product-card:active { transform: scale(0.96); }
751
+ .product-image-container { width: 100%; height: 100%; }
752
+ .product-image-container img { width: 100%; height: 100%; object-fit: cover; }
753
+ .product-info-overlay { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, transparent 100%); padding: 20px 12px 10px; color: #fff; pointer-events: none; }
754
+ .product-price { font-size: 1rem; font-weight: 600; }
755
+ .top-product-indicator { position: absolute; top: 8px; right: 8px; background-color: var(--accent); color: var(--text-on-accent); padding: 2px 8px; font-size: 0.7rem; border-radius: 10px; font-weight: bold; z-index: 10; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  .no-results-message { padding: 40px 20px; text-align: center; font-size: 1.1rem; color: #ccc; }
757
+ .floating-buttons-container { position: fixed; bottom: 25px; right: 25px; display: flex; flex-direction: column; gap: 15px; z-index: 1000; }
758
+ .floating-button { background-color: var(--accent); color: var(--text-on-accent); border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); text-decoration: none; }
759
+ .floating-button:hover { background-color: var(--accent-hover); transform: translateY(-3px); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  #cart-button { position: relative; }
761
+ #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); }
 
 
 
 
 
 
 
 
 
 
 
762
  .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; }
763
+ .modal-content { background: #ffffff; color: #333; 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; }
764
+ body[data-theme*="dark"] .modal-content, body[data-theme*="cosmic"] .modal-content, body[data-theme*="crimson"] .modal-content, body[data-theme*="cyberpunk"] .modal-content { background: #2a2a2a; color: var(--text-light); }
765
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
766
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
767
  .close:hover { color: #666; }
768
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
769
+ .cart-item { display: grid; grid-template-columns: 60px 1fr auto auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #e0e0e0; }
770
+ body[data-theme*="dark"] .cart-item { border-bottom-color: #444; }
771
  .cart-item:last-child { border-bottom: none; }
772
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
773
  .cart-item-details { grid-column: 2; }
774
+ .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; }
775
  .cart-item-details .variant-info { font-size: 0.85rem; color: #666; }
776
  .cart-item-price { font-size: 0.9rem; color: #666; }
777
+ .cart-item-price .wholesale-active { color: #4CAF50; font-weight: bold; }
778
+ body[data-theme*="dark"] .cart-item-price { color: #ccc; }
779
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; grid-column: 3;}
780
  .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; }
781
+ body[data-theme*="dark"] .quantity-btn { background-color: #444; border-color: #555; color: #fff; }
782
  .cart-item-total { font-weight: bold; text-align: right; grid-column: 4; font-size: 1rem; color: var(--bg-medium);}
783
  .cart-item-remove { grid-column: 5; background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; padding: 5px; line-height: 1; }
784
  .cart-item-remove:hover { color: var(--danger-hover); }
785
  .quantity-input, .options-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; }
786
+ body[data-theme*="dark"] .quantity-input, body[data-theme*="dark"] .options-select { background-color: #333; color: #fff; border-color: #555; }
787
  .quantity-input:focus, .options-select:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 2px rgba(72, 209, 204, 0.2); }
788
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
789
+ body[data-theme*="dark"] .cart-summary { border-top-color: #444; }
790
  .cart-summary strong { font-size: 1.2rem; color: var(--bg-medium);}
791
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
792
  .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
  .product-button i { margin-right: 5px; }
794
  .clear-cart { background-color: #6c757d; }
795
  .clear-cart:hover { background-color: #5a6268; }
796
+ .formulate-order-button { background-color: var(--accent); color: var(--text-on-accent); }
797
  .formulate-order-button:hover { background-color: var(--accent-hover); }
798
+ .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--accent); color: var(--text-on-accent); 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;}
799
  .notification.show { opacity: 1;}
800
+ @media (max-width: 600px) {
801
+ .cart-item { grid-template-columns: 60px 1fr 40px; grid-template-rows: auto auto; row-gap: 5px;}
802
+ .cart-item-details { grid-column: 2; grid-row: 1; }
803
+ .cart-item-price { display: none; }
804
+ .cart-item-quantity { grid-column: 1 / 2; grid-row: 2; }
805
+ .cart-item-total { grid-column: 2; grid-row: 2; text-align: left; }
806
+ .cart-item-remove { grid-column: 3; grid-row: 1 / 3; }
807
+ .cart-actions { flex-direction: column; }
808
+ }
809
  </style>
810
  </head>
811
+ <body data-theme="{{ settings.color_scheme }}">
812
  <div class="container">
813
  <div class="top-bar">
814
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="logo">
 
1072
  return;
1073
  }
1074
 
 
 
 
 
 
 
1075
  const cartItemId = `${product.product_id}-${color}-${size}`;
1076
  const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
1077
  if (existingItemIndex > -1) {
 
1081
  id: cartItemId,
1082
  product_id: product.product_id,
1083
  name: product.name,
1084
+ retail_price: product.price,
1085
+ wholesale_price: product.wholesale_price,
1086
+ min_wholesale_quantity: product.min_wholesale_quantity,
1087
  photo: product.photos && product.photos.length > 0 ? product.photos[0] : null,
1088
  quantity: quantity,
1089
  color: color,
1090
  size: size
1091
  });
1092
  }
1093
+ saveCart();
1094
  closeModal('quantityModal');
1095
  updateCartButton();
1096
  showNotification(`${product.name} добавлен в корзину!`);
1097
  }
1098
 
1099
+ function getApplicablePrice(item) {
1100
+ const product = getProductById(item.product_id);
1101
+ if (!product) return item.retail_price;
1102
+
1103
+ let price = item.retail_price;
1104
+ let variantKey = [item.color, item.size].filter(v => v !== 'N/A').join('-');
1105
+ if (variantKey && product.variant_prices && product.variant_prices[variantKey]) {
1106
+ price = product.variant_prices[variantKey];
1107
+ }
1108
+
1109
+ if (product.wholesale_price != null && product.min_wholesale_quantity != null && item.quantity >= product.min_wholesale_quantity) {
1110
+ price = product.wholesale_price;
1111
+ }
1112
+ return price;
1113
+ }
1114
+
1115
+ function saveCart() {
1116
+ localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1117
+ updateCartButton();
1118
+ }
1119
+
1120
  function updateCartButton() {
1121
  const cartCountElement = document.getElementById('cart-count');
1122
  const cartButton = document.getElementById('cart-button');
 
1142
  cartTotalElement.textContent = '0.00';
1143
  } else {
1144
  cartContent.innerHTML = cart.map(item => {
1145
+ const price = getApplicablePrice(item);
1146
+ const itemTotal = price * item.quantity;
1147
  total += itemTotal;
1148
+ const photoUrl = item.photo ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}` : 'https://via.placeholder.com/60x60.png?text=N/A';
 
 
1149
 
1150
  let variantInfo = [];
1151
  if (item.color && item.color !== 'N/A') variantInfo.push(`Цвет: ${item.color}`);
1152
  if (item.size && item.size !== 'N/A') variantInfo.push(`Размер: ${item.size}`);
1153
 
1154
+ const isWholesale = item.wholesale_price != null && item.min_wholesale_quantity != null && item.quantity >= item.min_wholesale_quantity;
1155
+ const priceClass = isWholesale ? 'wholesale-active' : '';
1156
+
1157
  return `
1158
+ <div class="cart-item" data-item-id="${item.id}">
1159
  <img src="${photoUrl}" alt="${item.name}">
1160
  <div class="cart-item-details">
1161
  <strong>${item.name}</strong>
1162
  <p class="variant-info">${variantInfo.join(', ')}</p>
1163
+ <p class="cart-item-price ${priceClass}">${price.toFixed(2)} ${currencyCode} ${isWholesale ? '(Опт)' : ''}</p>
1164
  </div>
1165
  <div class="cart-item-quantity">
1166
  <button class="quantity-btn" onclick="decrementCartItem('${item.id}')">-</button>
 
1185
  const itemIndex = cart.findIndex(item => item.id === itemId);
1186
  if (itemIndex > -1) {
1187
  cart[itemIndex].quantity++;
1188
+ saveCart();
1189
  openCartModal();
 
1190
  }
1191
  }
1192
 
 
1197
  if (cart[itemIndex].quantity <= 0) {
1198
  cart.splice(itemIndex, 1);
1199
  }
1200
+ saveCart();
1201
  openCartModal();
 
1202
  }
1203
  }
1204
 
1205
  function removeFromCart(itemId) {
1206
  cart = cart.filter(item => item.id !== itemId);
1207
+ saveCart();
1208
  openCartModal();
 
1209
  }
1210
 
1211
  function clearCart() {
 
1222
  alert("Корзина пуста! Добавьте товары перед формированием заказа.");
1223
  return;
1224
  }
1225
+
1226
+ const orderCart = cart.map(item => ({
1227
+ ...item,
1228
+ price: getApplicablePrice(item)
1229
+ }));
1230
+
1231
+ const orderData = { cart: orderCart };
1232
  const formulateButton = document.querySelector('.formulate-order-button');
1233
  if (formulateButton) formulateButton.disabled = true;
1234
  showNotification("Формируем заказ...", 5000);
1235
+
1236
  fetch(`/${envId}/create_order`, {
1237
  method: 'POST',
1238
  headers: { 'Content-Type': 'application/json' },
 
1342
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1343
  <style>
1344
  {% if settings.color_scheme == 'forest' %}
1345
+ :root { --bg-dark: #2F4F4F; --bg-medium: #556B2F; --accent: #90EE90; --accent-hover: #72b372; --text-light: #F5F5DC; --text-dark: #333; --danger: #CD5C5C; --danger-hover: #F08080; --chat-bg: #e4e9d5; --text-on-accent: #2F4F4F; --user-bubble: #556B2F; --user-text: #F5F5DC; --ai-bubble: #ffffff; --ai-text: #333;}
1346
  {% elif settings.color_scheme == 'ocean' %}
1347
+ :root { --bg-dark: #000080; --bg-medium: #1E90FF; --accent: #87CEEB; --accent-hover: #65b2d6; --text-light: #F0F8FF; --text-dark: #333; --danger: #FF6347; --danger-hover: #FF4500; --chat-bg: #e0f0ff; --text-on-accent: #000080; --user-bubble: #1E90FF; --user-text: #F0F8FF; --ai-bubble: #ffffff; --ai-text: #333;}
1348
  {% elif settings.color_scheme == 'sunset' %}
1349
+ :root { --bg-dark: #8B4513; --bg-medium: #D2691E; --accent: #FFA500; --accent-hover: #d18700; --text-light: #FFF8DC; --text-dark: #333; --danger: #DC143C; --danger-hover: #FF0000; --chat-bg: #fff0d4; --text-on-accent: #8B4513; --user-bubble: #D2691E; --user-text: #FFF8DC; --ai-bubble: #ffffff; --ai-text: #333;}
1350
  {% elif settings.color_scheme == 'lavender' %}
1351
+ :root { --bg-dark: #483D8B; --bg-medium: #9370DB; --accent: #E6E6FA; --accent-hover: #c4c4e0; --text-light: #F0F8FF; --text-dark: #333; --danger: #DB7093; --danger-hover: #FFC0CB; --chat-bg: #f5f0ff; --text-on-accent: #483D8B; --user-bubble: #9370DB; --user-text: #F0F8FF; --ai-bubble: #ffffff; --ai-text: #333;}
1352
  {% elif settings.color_scheme == 'vintage' %}
1353
+ :root { --bg-dark: #5D4037; --bg-medium: #A1887F; --accent: #D7CCC8; --accent-hover: #b7a9a4; --text-light: #F5F5F5; --text-dark: #3E2723; --danger: #BF360C; --danger-hover: #F4511E; --chat-bg: #EFEBE9; --text-on-accent: #3E2723; --user-bubble: #A1887F; --user-text: #F5F5F5; --ai-bubble: #ffffff; --ai-text: #3E2723;}
1354
  {% elif settings.color_scheme == 'dark' %}
1355
+ :root { --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; --text-on-accent: #1F1F1F; --user-bubble: #BB86FC; --user-text: #121212; --ai-bubble: #333333; --ai-text: #E1E1E1;}
1356
  {% elif settings.color_scheme == 'cosmic' %}
1357
+ :root { --bg-dark: #2c003e; --bg-medium: #4a0072; --accent: #f929ff; --accent-hover: #ff5fd8; --text-light: #f3e5f5; --text-dark: #ffffff; --danger: #e91e63; --danger-hover: #f06292; --chat-bg: #1a0024; --text-on-accent: #2c003e; --user-bubble: #f929ff; --user-text: #2c003e; --ai-bubble: #4a0072; --ai-text: #f3e5f5;}
1358
  {% elif settings.color_scheme == 'minty' %}
1359
+ :root { --bg-dark: #004D40; --bg-medium: #00796B; --accent: #4DB6AC; --accent-hover: #80CBC4; --text-light: #E0F2F1; --text-dark: #004D40; --danger: #ef5350; --danger-hover: #e57373; --chat-bg: #E0F2F1; --text-on-accent: #E0F2F1; --user-bubble: #00796B; --user-text: #E0F2F1; --ai-bubble: #ffffff; --ai-text: #004D40;}
1360
  {% elif settings.color_scheme == 'mocha' %}
1361
+ :root { --bg-dark: #3e2723; --bg-medium: #5d4037; --accent: #a1887f; --accent-hover: #bcaaa4; --text-light: #efebe9; --text-dark: #ffffff; --danger: #d32f2f; --danger-hover: #e57373; --chat-bg: #d7ccc8; --text-on-accent: #3e2723; --user-bubble: #5d4037; --user-text: #efebe9; --ai-bubble: #ffffff; --ai-text: #3e2723;}
1362
  {% elif settings.color_scheme == 'crimson' %}
1363
+ :root { --bg-dark: #121212; --bg-medium: #b71c1c; --accent: #ff5252; --accent-hover: #ff8a80; --text-light: #ffffff; --text-dark: #ffffff; --danger: #c62828; --danger-hover: #e53935; --chat-bg: #212121; --text-on-accent: #ffffff; --user-bubble: #ff5252; --user-text: #121212; --ai-bubble: #424242; --ai-text: #ffffff;}
1364
  {% elif settings.color_scheme == 'solar' %}
1365
+ :root { --bg-dark: #212121; --bg-medium: #424242; --accent: #ffca28; --accent-hover: #ffd54f; --text-light: #212121; --text-dark: #ffffff; --danger: #d32f2f; --danger-hover: #e57373; --chat-bg: #fff8e1; --text-on-accent: #212121; --user-bubble: #424242; --user-text: #ffffff; --ai-bubble: #ffffff; --ai-text: #212121;}
1366
  {% elif settings.color_scheme == 'cyberpunk' %}
1367
+ :root { --bg-dark: #000000; --bg-medium: #0d0221; --accent: #00f0ff; --accent-hover: #81f5ff; --text-light: #ffffff; --text-dark: #ffffff; --danger: #f50057; --danger-hover: #ff4081; --chat-bg: #0a0116; --text-on-accent: #0d0221; --user-bubble: #00f0ff; --user-text: #0d0221; --ai-bubble: #0d0221; --ai-text: #ffffff;}
1368
  {% else %}
1369
+ :root { --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; --text-on-accent: #003C43; --user-bubble: #135D66; --user-text: #E3FEF7; --ai-bubble: #ffffff; --ai-text: #333;}
1370
  {% endif %}
1371
 
1372
  * { margin: 0; padding: 0; box-sizing: border-box; }
 
1380
  height: 100%;
1381
  overflow: hidden;
1382
  }
1383
+ .chat-container { display: flex; flex-direction: column; height: 100%; width: 100%; max-width: 800px; margin: 0 auto; background: var(--chat-bg); box-shadow: 0 0 20px rgba(0,0,0,0.05); }
1384
+ .chat-header { display: flex; align-items: center; padding: 10px 15px; background: var(--bg-dark); color: var(--text-light); flex-shrink: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1385
  .chat-header a { color: var(--text-light); font-size: 1.2rem; text-decoration: none; }
1386
  .chat-header .logo { width: 40px; height: 40px; border-radius: 50%; margin: 0 15px; border: 2px solid var(--accent); }
1387
  .chat-header h1 { font-size: 1.2rem; font-weight: 600; }
1388
+ #chat-messages { flex-grow: 1; overflow-y: auto; padding: 20px 15px; display: flex; flex-direction: column; gap: 12px; }
1389
+ .chat-message { display: flex; flex-direction: column; max-width: 85%; animation: message-appear 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
1390
+ @keyframes message-appear { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
1391
+ .message-bubble { padding: 10px 15px; border-radius: 18px; line-height: 1.5; word-wrap: break-word; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1392
  .chat-message.user { align-self: flex-end; }
1393
+ .chat-message.user .message-bubble { background-color: var(--user-bubble); color: var(--user-text); border-bottom-right-radius: 4px; }
 
 
 
 
1394
  .chat-message.ai { align-self: flex-start; }
1395
+ .chat-message.ai .message-bubble { background-color: var(--ai-bubble); color: var(--ai-text); border-bottom-left-radius: 4px; }
1396
+ .chat-input-container { padding: 15px; background: #fff; border-top: 1px solid #ddd; display: flex; gap: 10px; align-items: center; flex-shrink: 0; }
1397
+ body[data-theme*="dark"] .chat-input-container, body[data-theme*="cosmic"] .chat-input-container, body[data-theme*="crimson"] .chat-input-container, body[data-theme*="cyberpunk"] .chat-input-container { background: var(--bg-dark); border-top: 1px solid var(--bg-medium);}
1398
+ #chat-input { flex-grow: 1; padding: 12px 18px; border: 1px solid #e0e0e0; border-radius: 24px; font-size: 1rem; outline: none; transition: border-color 0.3s, box-shadow 0.3s; }
1399
+ body[data-theme*="dark"] #chat-input, body[data-theme*="cosmic"] #chat-input, body[data-theme*="crimson"] #chat-input, body[data-theme*="cyberpunk"] #chat-input { background-color: var(--bg-medium); color: var(--text-light); border-color: var(--bg-medium); }
1400
+ #chat-input:focus { border-color: var(--bg-medium); box-shadow: 0 0 0 3px rgba(19, 93, 102, 0.15); }
1401
+ #chat-send-button { background-color: var(--bg-medium); color: var(--text-light); border: none; border-radius: 50%; width: 48px; height: 48px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: background-color 0.3s, transform 0.2s; flex-shrink: 0; font-size: 1.2rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1402
  #chat-send-button:hover { background-color: var(--bg-dark); }
1403
  #chat-send-button:active { transform: scale(0.9); }
1404
  #chat-send-button:disabled { background-color: #cccccc; cursor: not-allowed; }
1405
  .floating-buttons-container { position: fixed; bottom: 25px; right: 25px; z-index: 1000; }
1406
+ .floating-button { background-color: var(--accent); color: var(--text-on-accent); border: none; border-radius: 50%; width: 55px; height: 55px; font-size: 1.5rem; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 4px 15px rgba(72, 209, 204, 0.4); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
1407
  .floating-button:hover { background-color: var(--accent-hover); transform: translateY(-3px); }
1408
  #cart-button { position: relative; }
1409
  #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); }
1410
  .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; }
1411
+ .modal-content { background: #ffffff; color: #333; 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; }
1412
+ body[data-theme*="dark"] .modal-content, body[data-theme*="cosmic"] .modal-content, body[data-theme*="crimson"] .modal-content, body[data-theme*="cyberpunk"] .modal-content { background-color: #2a2a2a; color: var(--text-light); }
1413
  @keyframes slideIn { from { transform: translateY(-30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
1414
  .close { position: absolute; top: 15px; right: 15px; font-size: 1.8rem; color: #aaa; cursor: pointer; transition: color 0.3s; line-height: 1; }
1415
  .close:hover { color: #666; }
1416
  .modal-content h2 { margin-top: 0; margin-bottom: 20px; color: var(--bg-medium); display: flex; align-items: center; gap: 10px;}
1417
+ .cart-item { display: grid; grid-template-columns: 60px 1fr auto auto; gap: 15px; align-items: center; padding: 15px 0; border-bottom: 1px solid #e0e0e0; }
1418
+ body[data-theme*="dark"] .cart-item, body[data-theme*="cosmic"] .cart-item, body[data-theme*="crimson"] .cart-item, body[data-theme*="cyberpunk"] .cart-item { border-bottom-color: #444; }
1419
  .cart-item:last-child { border-bottom: none; }
1420
  .cart-item img { width: 60px; height: 60px; object-fit: cover; border-radius: 8px; }
1421
+ .cart-item-details strong { display: block; margin-bottom: 5px; font-size: 1rem; }
 
1422
  .cart-item-quantity { display: flex; align-items: center; gap: 8px; }
1423
  .quantity-btn { background-color: #eee; border: 1px solid #ddd; border-radius: 50%; width: 28px; height: 28px; cursor: pointer; }
1424
+ body[data-theme*="dark"] .quantity-btn, body[data-theme*="cosmic"] .quantity-btn, body[data-theme*="crimson"] .quantity-btn, body[data-theme*="cyberpunk"] .quantity-btn { background-color: #444; border-color: #555; color: #fff; }
1425
  .cart-item-total { font-weight: bold; text-align: right; font-size: 1rem; color: var(--bg-medium);}
1426
  .cart-item-remove { background:none; border:none; color: var(--danger); cursor:pointer; font-size: 1.3em; }
1427
  .quantity-input, .options-select { width: 100%; max-width: 180px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 1rem; margin: 10px 0; }
1428
+ body[data-theme*="dark"] .quantity-input, body[data-theme*="dark"] .options-select { background-color: #333; color: #fff; border-color: #555; }
1429
  .cart-summary { margin-top: 20px; text-align: right; border-top: 1px solid #e0e0e0; padding-top: 15px; }
1430
+ body[data-theme*="dark"] .cart-summary { border-top-color: #444; }
1431
  .cart-actions { margin-top: 25px; display: flex; justify-content: space-between; }
1432
+ .product-button { display: block; width: auto; flex-grow: 1; padding: 10px; border: none; border-radius: 8px; color: var(--text-on-accent); cursor: pointer; text-align: center; text-decoration: none; }
1433
+ .clear-cart { background-color: #6c757d; color: #ffffff;}
1434
+ .formulate-order-button { background-color: var(--accent); }
1435
+ .notification { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background-color: var(--accent); color: var(--text-on-accent); 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;}
1436
  .notification.show { opacity: 1;}
1437
+ .chat-product-card { background-color: var(--ai-bubble); border-radius: 12px; padding: 10px; margin-top: 8px; display: flex; align-items: center; gap: 12px; border: 1px solid #e0e0e0; }
 
1438
  .chat-product-card img { width: 50px; height: 50px; object-fit: cover; border-radius: 8px; flex-shrink: 0; }
1439
  .chat-product-card-info { flex-grow: 1; }
1440
+ .chat-product-card-info strong { display: block; font-size: 0.9rem; color: var(--ai-text); margin-bottom: 2px; }
 
1441
  .chat-product-card-info span { font-size: 0.85rem; color: var(--bg-medium); font-weight: 500; }
1442
  .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
1443
+ .chat-product-link, .chat-add-to-cart { display: inline-block; background-color: var(--accent); color: var(--text-on-accent); 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%; }
1444
+ .chat-product-link:hover, .chat-add-to-cart:hover { background-color: var(--accent-hover); }
 
1445
  </style>
1446
  </head>
1447
+ <body data-theme="{{ settings.color_scheme }}">
1448
  <div class="chat-container">
1449
  <div class="chat-header">
1450
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
 
1581
  }
1582
  const product = getProductById(selectedProductId);
1583
 
 
 
 
 
 
 
1584
  const cartItemId = `${product.product_id}-${color}-${size}`;
1585
+ const existingItemIndex = cart.findIndex(item => item.id === cartItemId);
1586
+ if (existingItemIndex > -1) {
1587
+ cart[existingItemIndex].quantity += quantity;
1588
  } else {
1589
+ cart.push({
1590
+ id: cartItemId,
1591
+ product_id: product.product_id,
1592
+ name: product.name,
1593
+ retail_price: product.price,
1594
+ wholesale_price: product.wholesale_price,
1595
+ min_wholesale_quantity: product.min_wholesale_quantity,
1596
+ photo: product.photos ? product.photos[0] : null,
1597
+ quantity,
1598
+ color,
1599
+ size
1600
+ });
1601
  }
1602
  localStorage.setItem(`mekaCart_${envId}`, JSON.stringify(cart));
1603
  closeModal('quantityModal');
1604
  updateCartButton();
1605
  showNotification(`${product.name} добавлен в корзину!`);
1606
  }
1607
+ function getApplicablePrice(item) {
1608
+ const product = getProductById(item.product_id);
1609
+ if (!product) return item.retail_price;
1610
+ let price = item.retail_price;
1611
+ let variantKey = [item.color, item.size].filter(v => v !== 'N/A').join('-');
1612
+ if (variantKey && product.variant_prices && product.variant_prices[variantKey]) {
1613
+ price = product.variant_prices[variantKey];
1614
+ }
1615
+ if (product.wholesale_price != null && product.min_wholesale_quantity != null && item.quantity >= product.min_wholesale_quantity) {
1616
+ price = product.wholesale_price;
1617
+ }
1618
+ return price;
1619
+ }
1620
+
1621
  function updateCartButton() {
1622
  const cartCountEl = document.getElementById('cart-count');
1623
  const cartButton = document.getElementById('cart-button');
 
1640
  cartTotalEl.textContent = '0.00';
1641
  } else {
1642
  cartContent.innerHTML = cart.map(item => {
1643
+ const price = getApplicablePrice(item);
1644
+ const itemTotal = price * item.quantity;
1645
  total += itemTotal;
1646
  let variantInfo = [];
1647
  if (item.color && item.color !== 'N/A') variantInfo.push(`Цвет: ${item.color}`);
1648
  if (item.size && item.size !== 'N/A') variantInfo.push(`Размер: ${item.size}`);
1649
+ const isWholesale = item.wholesale_price != null && item.min_wholesale_quantity != null && item.quantity >= item.min_wholesale_quantity;
1650
  return `<div class="cart-item">
1651
  <img src="${item.photo ? `https://huggingface.co/datasets/${repoId}/resolve/main/photos/${item.photo}` : ''}" alt="${item.name}">
1652
+ <div><strong>${item.name}</strong><p>${variantInfo.join(', ')}</p><p>${price.toFixed(2)} ${currencyCode} ${isWholesale ? '(Опт)' : ''}</p></div>
1653
  <div class="cart-item-quantity"><button class="quantity-btn" onclick="decrementCartItem('${item.id}')">-</button><span>${item.quantity}</span><button class="quantity-btn" onclick="incrementCartItem('${item.id}')">+</button></div>
1654
  <span class="cart-item-total">${itemTotal.toFixed(2)}</span>
1655
  <button class="cart-item-remove" onclick="removeFromCart('${item.id}')"><i class="fas fa-trash-alt"></i></button>
 
1692
  }
1693
  function formulateOrder() {
1694
  if (cart.length === 0) return;
1695
+ const orderCart = cart.map(item => ({...item, price: getApplicablePrice(item)}));
1696
+ fetch(`/${envId}/create_order`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cart: orderCart }) })
1697
  .then(res => res.json())
1698
  .then(data => {
1699
  if (data.order_id) {
 
1723
  const messageElement = document.createElement('div');
1724
  messageElement.className = `chat-message ${role}`;
1725
 
1726
+ const productRegex = /\[ID_ТОВАРА:\s*([a-fA-F0-9]+)\s*Название:\s*([^\]]+)\]/g;
1727
  let lastIndex = 0;
1728
  const contentFragment = document.createDocumentFragment();
1729
  let match;
 
1732
  if (match.index > lastIndex) {
1733
  const textNode = document.createElement('div');
1734
  textNode.className = 'message-bubble';
1735
+ textNode.innerHTML = text.substring(lastIndex, match.index).replace(/\n/g, '<br>');
1736
  messageElement.appendChild(textNode);
1737
  }
1738
  const productId = match[1];
 
1758
  if (lastIndex < text.length) {
1759
  const textNode = document.createElement('div');
1760
  textNode.className = 'message-bubble';
1761
+ textNode.innerHTML = text.substring(lastIndex).replace(/\n/g, '<br>');
1762
  messageElement.appendChild(textNode);
1763
  }
1764
 
 
1871
  </div>
1872
 
1873
  <div style="text-align:center; margin-top:20px; padding: 0 10px;">
1874
+ <p style="font-size: 1.5rem; font-weight: bold; color: #135D66; margin-bottom: 5px;"><strong>Цена:</strong> <span id="variantPrice">{{ "%.0f"|format(product.price) }} {{ currency_code }}</span></p>
1875
+ {% if product.get('wholesale_price') and product.get('min_wholesale_quantity') %}
1876
+ <p style="font-size: 0.9rem; color: #4CAF50; margin-bottom: 15px;">
1877
+ Оптом: <strong>{{ "%.0f"|format(product.wholesale_price) }} {{ currency_code }}</strong> от {{ product.min_wholesale_quantity }} шт.
1878
+ </p>
1879
+ {% endif %}
1880
  <button class="product-button formulate-order-button" style="padding: 12px 30px; width: 100%; max-width: 300px;" onclick="closeModal('productModal'); openQuantityModalById('{{ product.get('product_id', '') }}')">
1881
  <i class="fas fa-cart-plus"></i> В корзину
1882
  </button>
 
1938
  .catalog-link { display: block; text-align: center; margin-top: 25px; color: var(--bg-medium); text-decoration: none; font-size: 0.9rem; }
1939
  .catalog-link:hover { text-decoration: underline; }
1940
  .not-found { text-align: center; color: #dc3545; font-size: 1.2rem; padding: 40px 0;}
1941
+
1942
+ @media (max-width: 600px) {
1943
+ .container { padding: 15px; }
1944
+ h1 { font-size: 1.5rem; }
1945
+ h2 { font-size: 1.2rem; }
1946
+ .order-item { grid-template-columns: 45px 1fr auto; grid-template-rows: auto auto; }
1947
+ .order-item img { width: 45px; height: 45px; }
1948
+ .item-details { grid-column: 2 / 4; }
1949
+ .item-quantity { grid-row: 2; grid-column: 1 / 3; }
1950
+ .item-total { grid-row: 2; grid-column: 3; }
1951
+ }
1952
  </style>
1953
  </head>
1954
  <body>
 
1991
  let variantInfo = [];
1992
  if (item.color && item.color !== 'N/A') variantInfo.push(`Цвет: ${item.color}`);
1993
  if (item.size && item.size !== 'N/A') variantInfo.push(`Размер: ${item.size}`);
1994
+ const isWholesale = item.min_wholesale_quantity && item.quantity >= item.min_wholesale_quantity;
1995
 
1996
  return `
1997
  <div class="order-item">
1998
  <img src="${item.photo_url}" alt="${item.name}">
1999
  <div class="item-details">
2000
+ <strong>${item.name} ${isWholesale ? '(Опт)' : ''}</strong>
2001
  <span>${variantInfo.join(', ')}</span>
2002
  <span>${item.price.toFixed(2)} {{ currency_code }}</span>
2003
  </div>
 
2060
  if (item.color && item.color !== 'N/A') variantInfo.push(item.color);
2061
  if (item.size && item.size !== 'N/A') variantInfo.push(item.size);
2062
  let variantText = variantInfo.length > 0 ? ` (${variantInfo.join(', ')})` : '';
2063
+ const isWholesale = item.min_wholesale_quantity && item.quantity >= item.min_wholesale_quantity;
2064
 
2065
+ message += `*${item.name}*${variantText} ${isWholesale ? '(Опт)' : ''}%0A`;
2066
  message += ` - Количество: ${item.quantity}%0A`;
2067
  message += ` - Цена: ${item.price.toFixed(2)} {{ currency_code }}%0A`;
2068
  });
 
2184
  .variant-price-item { display: flex; align-items: center; gap: 8px; font-size: 0.9em; }
2185
  .variant-price-item label { margin-top: 0; white-space: nowrap; }
2186
  .variant-price-item input { margin-top: 0; }
2187
+ .wholesale-fields { border-left: 3px solid #17a2b8; padding-left: 15px; margin-left: -18px; margin-top: 10px; }
2188
  </style>
2189
  </head>
2190
  <body>
 
2239
  <label for="organization_name">Название организации:</label>
2240
  <input type="text" id="organization_name" name="organization_name" value="{{ settings.organization_name }}">
2241
 
2242
+ <label for="business_type">Тип бизнеса:</label>
2243
+ <select id="business_type" name="business_type">
2244
+ {% for key, name in business_types.items() %}
2245
+ <option value="{{ key }}" {% if settings.business_type == key %}selected{% endif %}>{{ name }}</option>
2246
+ {% endfor %}
2247
+ </select>
2248
+
2249
  <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
2250
  <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.whatsapp_number }}" placeholder="+996XXXXXXXXX">
2251
 
 
2370
  <input type="hidden" name="action" value="add_product">
2371
  <label for="add_name">Название товара *:</label>
2372
  <input type="text" id="add_name" name="name" required>
2373
+ <label for="add_price">
2374
+ {% if settings.business_type == 'wholesale' %}Оптовая цена{% else %}Розничная цена{% endif %} ({{ currency_code }}) *:
2375
+ </label>
2376
  <input type="number" id="add_price" name="price" step="0.01" min="0" required>
2377
+
2378
+ {% if settings.business_type in ['wholesale', 'both'] %}
2379
+ <div class="wholesale-fields">
2380
+ {% if settings.business_type == 'both' %}
2381
+ <label for="add_wholesale_price">Оптовая Цена ({{ currency_code }}):</label>
2382
+ <input type="number" id="add_wholesale_price" name="wholesale_price" step="0.01" min="0">
2383
+ {% endif %}
2384
+ <label for="add_min_wholesale_quantity">Минимальное количество для опта:</label>
2385
+ <input type="number" id="add_min_wholesale_quantity" name="min_wholesale_quantity" min="1">
2386
+ </div>
2387
+ {% endif %}
2388
+
2389
  <label for="add_photos">Фотографии (до 10 шт.):</label>
2390
  <input type="file" id="add_photos" name="photos" accept="image/*" multiple>
2391
  <label for="add_description">Описание:</label>
 
2470
  {% endif %}
2471
  </h3>
2472
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
2473
+ <p>
2474
+ <strong>{% if product.get('wholesale_price') %}Розница:{% else %}Цена:{% endif %}</strong> {{ "%.2f"|format(product.price) }} {{ currency_code }}
2475
+ </p>
2476
+ {% if product.get('wholesale_price') and product.get('min_wholesale_quantity') %}
2477
+ <p><strong>Опт:</strong> {{ "%.2f"|format(product.wholesale_price) }} {{ currency_code }} (от {{ product.min_wholesale_quantity }} шт.)</p>
2478
+ {% endif %}
2479
  <p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>
2480
  {% set colors = product.get('colors', []) %}
2481
  {% set sizes = product.get('sizes', []) %}
2482
  <p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
2483
  <p><strong>Размеры/Объем:</strong> {{ sizes|select('ne', '')|join(', ') if sizes|select('ne', '')|list|length > 0 else 'Нет' }}</p>
 
2484
  {% if product.get('photos') and product['photos']|length > 1 %}
2485
  <p style="font-size: 0.8rem; color: #999;">(Всего фото: {{ product['photos']|length }})</p>
2486
  {% endif %}
 
2503
  <input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}">
2504
  <label>Название *:</label>
2505
  <input type="text" name="name" value="{{ product['name'] }}" required>
2506
+ <label>
2507
+ {% if settings.business_type == 'wholesale' %}Оптовая цена{% else %}Розничная цена{% endif %} ({{ currency_code }}) *:
2508
+ </label>
2509
  <input type="number" name="price" step="0.01" min="0" value="{{ product['price'] }}" required>
2510
+
2511
+ {% if settings.business_type in ['wholesale', 'both'] %}
2512
+ <div class="wholesale-fields">
2513
+ {% if settings.business_type == 'both' %}
2514
+ <label>Оптовая Цена ({{ currency_code }}):</label>
2515
+ <input type="number" name="wholesale_price" step="0.01" min="0" value="{{ product.get('wholesale_price', '') }}">
2516
+ {% endif %}
2517
+ <label>Минимальное количество для опта:</label>
2518
+ <input type="number" name="min_wholesale_quantity" min="1" value="{{ product.get('min_wholesale_quantity', '') }}">
2519
+ </div>
2520
+ {% endif %}
2521
+
2522
  <label>Заменить фотографии (выберите новые файлы, до 10 шт.):</label>
2523
  <input type="file" id="edit_photos_{{ loop.index0 }}" name="photos" accept="image/*" multiple>
2524
  {% if product.get('photos') %}
 
2862
 
2863
  environments_data.append({
2864
  "id": env_id,
2865
+ "business_type": settings.get('business_type', 'retail'),
2866
  "chat_active": is_active,
2867
  "expires_soon": expires_soon,
2868
  "expires_date": expires_date_str
2869
  })
2870
 
2871
  environments_data.sort(key=lambda x: x['id'])
2872
+ return render_template_string(ADMHOSTO_TEMPLATE, environments=environments_data, business_types=BUSINESS_TYPES)
2873
 
2874
  @app.route('/admhosto/create', methods=['POST'])
2875
  def create_environment():
2876
+ business_type = request.form.get('business_type')
2877
+ if not business_type or business_type not in BUSINESS_TYPES:
2878
+ flash('Необходимо выбрать корректный тип бизнеса.', 'error')
2879
+ return redirect(url_for('admhosto'))
2880
+
2881
  all_data = load_data()
2882
  while True:
2883
  new_id = ''.join(random.choices(string.digits, k=6))
 
2895
  "contact": "Наш магазин находится по адресу: ... Связаться с нами можно по телефону ..."
2896
  },
2897
  'settings': {
2898
+ "organization_name": f"Магазин {new_id}",
2899
  "whatsapp_number": "+996701202013",
2900
  "currency_code": "KGS",
2901
  "chat_name": "EVA",
2902
  "chat_avatar": None,
2903
  "color_scheme": "default",
2904
  "chat_activated": False,
2905
+ "chat_activation_expires": None,
2906
+ "business_type": business_type
2907
  },
2908
  'chats': {}
2909
  }
2910
  save_data(all_data)
2911
+ flash(f'Новая среда с ID {new_id} ({BUSINESS_TYPES[business_type]}) успешно создана.', 'success')
2912
  return redirect(url_for('admhosto'))
2913
 
2914
  @app.route('/admhosto/delete/<env_id>', methods=['POST'])
 
3058
  "color": item.get('color', 'N/A'),
3059
  "size": item.get('size', 'N/A'),
3060
  "photo": item.get('photo'),
3061
+ "min_wholesale_quantity": item.get('min_wholesale_quantity'),
3062
  "photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photo']}" if item.get('photo') else "https://via.placeholder.com/60x60.png?text=N/A"
3063
  })
3064
  total_price += price * quantity
 
3159
 
3160
  elif action == 'update_settings':
3161
  settings['organization_name'] = request.form.get('organization_name', 'Gippo312').strip()
3162
+ settings['business_type'] = request.form.get('business_type', 'retail')
3163
  settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
3164
  settings['currency_code'] = request.form.get('currency_code', 'KGS')
3165
  settings['chat_name'] = request.form.get('chat_name', 'EVA').strip()
 
3236
  flash("Неверный формат цены.", 'error')
3237
  return redirect(url_for('admin', env_id=env_id))
3238
 
3239
+ if settings.get('business_type') in ['wholesale', 'both']:
3240
+ wholesale_price_str = request.form.get('wholesale_price', '').replace(',', '.')
3241
+ min_qty_str = request.form.get('min_wholesale_quantity', '')
3242
+
3243
+ if settings.get('business_type') == 'wholesale':
3244
+ product_data['wholesale_price'] = product_data['price']
3245
+ else:
3246
+ try:
3247
+ product_data['wholesale_price'] = round(float(wholesale_price_str), 2) if wholesale_price_str else None
3248
+ except ValueError:
3249
+ product_data['wholesale_price'] = None
3250
+
3251
+ try:
3252
+ product_data['min_wholesale_quantity'] = int(min_qty_str) if min_qty_str else None
3253
+ except ValueError:
3254
+ product_data['min_wholesale_quantity'] = None
3255
+
3256
+ if product_data['wholesale_price'] is not None and product_data['min_wholesale_quantity'] is None:
3257
+ flash("Указана оптовая цена, но не указано минимальное количество. Укажите количество.", "warning")
3258
+ product_data['min_wholesale_quantity'] = 2
3259
+
3260
  variant_prices = {}
3261
  for key, value in request.form.items():
3262
  if key.startswith('variant_price_') and value:
 
3397
  chat_avatar_url=chat_avatar_url,
3398
  currencies=CURRENCIES,
3399
  color_schemes=COLOR_SCHEMES,
3400
+ business_types=BUSINESS_TYPES,
3401
  env_id=env_id,
3402
  chat_status=chat_status
3403
  )