Kgshop commited on
Commit
127da28
·
verified ·
1 Parent(s): 4a9487a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +263 -147
app.py CHANGED
@@ -34,8 +34,8 @@ HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
34
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
35
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
36
 
37
- DEFAULT_STORE_ADDRESS = "Рынок Кербент, 6 ряд , 43 контейнер"
38
- DEFAULT_WHATSAPP_NUMBER = "+996701202013"
39
 
40
  CURRENCIES = {
41
  'KGS': 'Кыргызский сом',
@@ -46,8 +46,13 @@ CURRENCIES = {
46
  'EUR': 'Евро'
47
  }
48
 
49
- DOWNLOAD_RETRIES = 3
50
- DOWNLOAD_DELAY = 5
 
 
 
 
 
51
 
52
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
53
 
@@ -168,30 +173,36 @@ def get_env_data(env_id):
168
  "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров. Мы предлагаем широкий ассортимент продукции, от электроники до товаров для дома, всегда стремясь к качеству и доступности. Наша миссия — сделать ваш шопинг приятным и удобным, предлагая только лучшие товары, тщательно отобранные для вас.",
169
  "shipping": "Доставка осуществляется по всему Кыргызстану. Стоимость и сроки доставки зависят от региона и веса товара. По Бишкеку доставка возможна в течение 1-2 рабочих дней, в регионы — от 3 до 7 дней. Для уточнения деталей свяжитесь с нами.",
170
  "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
171
- "contact": f"Наш магазин находится по адресу: {DEFAULT_STORE_ADDRESS}. Связаться с нами можно по телефону: {DEFAULT_WHATSAPP_NUMBER} или через WhatsApp по этому же номеру. Мы работаем ежедневно с 9:00 до 18:00.",
172
- "whatsapp_number": DEFAULT_WHATSAPP_NUMBER,
 
 
173
  "currency_code": "KGS",
174
- "chat_name": "Чат с EVA",
175
  "chat_avatar": None,
176
- "chat_color_theme": "teal"
177
  }
178
- env_data = all_data.get(env_id, {
179
- 'products': [],
180
- 'categories': [],
181
- 'orders': {},
182
- 'organization_info': default_organization_info,
183
- 'chats': {}
184
- })
 
185
 
186
  if 'products' not in env_data: env_data['products'] = []
187
  if 'categories' not in env_data: env_data['categories'] = []
188
  if 'orders' not in env_data: env_data['orders'] = {}
189
- if 'organization_info' not in env_data:
190
- env_data['organization_info'] = default_organization_info
191
- else:
192
- for key, value in default_organization_info.items():
193
- env_data['organization_info'].setdefault(key, value)
194
  if 'chats' not in env_data: env_data['chats'] = {}
 
 
 
 
 
 
 
195
 
196
  products_changed = False
197
  for product in env_data['products']:
@@ -199,11 +210,12 @@ def get_env_data(env_id):
199
  product['product_id'] = uuid4().hex
200
  products_changed = True
201
 
202
- if products_changed:
203
  save_env_data(env_id, env_data)
204
 
205
  return env_data
206
 
 
207
  def save_env_data(env_id, env_data):
208
  all_data = load_data()
209
  all_data[env_id] = env_data
@@ -283,10 +295,10 @@ def generate_chat_response(message, chat_history_from_client, env_id):
283
  products = data.get('products', [])
284
  categories = data.get('categories', [])
285
  organization_info = data.get('organization_info', {})
286
- currency_code = organization_info.get('currency_code', 'KGS')
287
- chat_name = organization_info.get('chat_name', 'Gippo312').replace('Чат с ', '')
288
-
289
-
290
  product_info_list = []
291
  for p in products:
292
  if p.get('in_stock', True):
@@ -465,6 +477,40 @@ CATALOG_TEMPLATE = '''
465
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
466
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
467
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  :root {
469
  --bg-dark: #003C43;
470
  --bg-medium: #135D66;
@@ -475,6 +521,8 @@ CATALOG_TEMPLATE = '''
475
  --danger: #E57373;
476
  --danger-hover: #EF5350;
477
  }
 
 
478
  * { margin: 0; padding: 0; box-sizing: border-box; }
479
  html { -webkit-tap-highlight-color: transparent; }
480
  body {
@@ -694,7 +742,6 @@ CATALOG_TEMPLATE = '''
694
  .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
695
  .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%; }
696
  .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
697
- .chat-product-card-actions .fa-cart-plus { font-size: 0.9em; }
698
 
699
  </style>
700
  </head>
@@ -702,7 +749,7 @@ CATALOG_TEMPLATE = '''
702
  <div class="container">
703
  <div class="top-bar">
704
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="logo">
705
- <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Gippo312 Logo">
706
  </a>
707
  <div class="search-wrapper">
708
  <i class="fas fa-search"></i>
@@ -1161,22 +1208,61 @@ CHAT_TEMPLATE = '''
1161
  <head>
1162
  <meta charset="UTF-8">
1163
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
1164
- <title>{{ chat_name }}</title>
1165
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1166
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
1167
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1168
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
1169
  :root {
1170
- --bg-dark: {{ chat_colors.get('bg-dark', '#003C43') }};
1171
- --bg-medium: {{ chat_colors.get('bg-medium', '#135D66') }};
1172
- --accent: {{ chat_colors.get('accent', '#48D1CC') }};
1173
- --accent-hover: {{ chat_colors.get('accent-hover', '#77E4D8') }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1174
  --text-light: #E3FEF7;
1175
  --text-dark: #333;
1176
  --danger: #E57373;
1177
  --danger-hover: #EF5350;
1178
  --chat-bg: #f0f2f5;
1179
  }
 
 
1180
  * { margin: 0; padding: 0; box-sizing: border-box; }
1181
  html { -webkit-tap-highlight-color: transparent; height: 100%; }
1182
  body {
@@ -1207,7 +1293,7 @@ CHAT_TEMPLATE = '''
1207
  flex-shrink: 0;
1208
  }
1209
  .chat-header a { color: var(--text-light); font-size: 1.2rem; text-decoration: none; }
1210
- .chat-header .logo { width: 40px; height: 40px; border-radius: 50%; margin: 0 15px; border: 2px solid var(--accent); object-fit: cover;}
1211
  .chat-header h1 { font-size: 1.2rem; font-weight: 600; }
1212
  #chat-messages {
1213
  flex-grow: 1;
@@ -1326,12 +1412,8 @@ CHAT_TEMPLATE = '''
1326
  <div class="chat-container">
1327
  <div class="chat-header">
1328
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
1329
- {% if chat_avatar %}
1330
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/avatars/{{ chat_avatar }}" alt="Logo" class="logo">
1331
- {% else %}
1332
- <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Logo" class="logo">
1333
- {% endif %}
1334
- <h1>{{ chat_name }}</h1>
1335
  </div>
1336
  <div id="chat-messages"></div>
1337
  <div class="chat-input-container">
@@ -1654,9 +1736,8 @@ CHAT_TEMPLATE = '''
1654
  window.addEventListener('click', e => { if (e.target.classList.contains('modal')) closeModal(e.target.id); });
1655
  window.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal').forEach(m => closeModal(m.id)); });
1656
 
1657
- const chatAssistantName = "{{ chat_name }}".replace('Чат с ', '');
1658
- addMessageToChatUI(`👋 Здравствуйте! Я ваш виртуальный помощник ${chatAssistantName}. Чем могу помочь?`, 'ai');
1659
- chatHistory.push({ role: 'ai', text: `Здравствуйте! Я ваш виртуальный помощник ${chatAssistantName}. Чем могу помочь?` });
1660
  });
1661
  </script>
1662
  </body>
@@ -1849,7 +1930,7 @@ ORDER_TEMPLATE = '''
1849
  return;
1850
  }
1851
  const orderId = document.getElementById('orderId').textContent;
1852
- const whatsappNumber = "{{ whatsapp_number }}".replace(/\\+/g, '');
1853
  let message = `Здравствуйте! Хочу подтвердить или изменить свой заказ на Gippo312:%0A%0A`;
1854
  message += `*Номер заказа:* ${orderId}%0A%0A`;
1855
 
@@ -1969,13 +2050,14 @@ ADMIN_TEMPLATE = '''
1969
  .chat-message .bubble { display: inline-block; padding: 8px 12px; border-radius: 15px; max-width: 80%; }
1970
  .chat-message.user .bubble { background-color: #dcf8c6; }
1971
  .chat-message.ai .bubble { background-color: #f1f1f1; }
 
1972
  </style>
1973
  </head>
1974
  <body>
1975
  <div class="container">
1976
  <div class="header">
1977
  <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
1978
- <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Gippo312 Logo">
1979
  <h1><i class="fas fa-tools"></i> Админ-панель Gippo312 (Среда: {{ env_id }})</h1>
1980
  </div>
1981
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="button" style="background-color: var(--bg-medium); color: white;"><i class="fas fa-store"></i> Перейти в каталог</a>
@@ -2001,9 +2083,50 @@ ADMIN_TEMPLATE = '''
2001
  </div>
2002
  <p style="font-size: 0.85rem; color: #999;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
2003
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2004
 
2005
  <div class="section">
2006
- <h2><i class="fas fa-comments"></i> Диалоги с EVA</h2>
2007
  <div class="item-list">
2008
  {% if chats %}
2009
  {% for chat_id, chat_data in chats.items()|sort(reverse=True) %}
@@ -2057,52 +2180,23 @@ ADMIN_TEMPLATE = '''
2057
 
2058
  <div class="flex-item">
2059
  <div class="section">
2060
- <h2><i class="fas fa-info-circle"></i> Настройки магазина и чата</h2>
2061
- <details open>
2062
  <summary><i class="fas fa-chevron-down"></i> Развернуть/Свернуть</summary>
2063
  <div class="form-content">
2064
- <form method="POST" enctype="multipart/form-data">
2065
  <input type="hidden" name="action" value="update_org_info">
2066
- <h3>Основные настройки</h3>
2067
- <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
2068
- <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ organization_info.get('whatsapp_number', '') }}" placeholder="+996701202013">
2069
- <label for="currency_code">Валюта магазина:</label>
2070
- <select id="currency_code" name="currency_code">
2071
- {% for code, name in currencies.items() %}
2072
- <option value="{{ code }}" {% if organization_info.get('currency_code') == code %}selected{% endif %}>{{ name }} ({{ code }})</option>
2073
- {% endfor %}
2074
- </select>
2075
-
2076
- <h3 style="margin-top:20px;">Настройки чата</h3>
2077
- <label for="chat_name">Имя чата:</label>
2078
- <input type="text" id="chat_name" name="chat_name" value="{{ organization_info.get('chat_name', '') }}">
2079
- <label for="chat_avatar">Аватар чата (загрузка заменит текущий):</label>
2080
- <input type="file" id="chat_avatar" name="chat_avatar" accept="image/*">
2081
- {% if organization_info.get('chat_avatar') %}
2082
- <div class="photo-preview" style="margin-top:5px;">
2083
- <p style="font-size:0.8rem; margin-bottom:5px;">Текущий аватар:</p>
2084
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/avatars/{{ organization_info.get('chat_avatar') }}" alt="Аватар чата">
2085
- </div>
2086
- {% endif %}
2087
- <label for="chat_color_theme">Цветовая схема чата:</label>
2088
- <select id="chat_color_theme" name="chat_color_theme">
2089
- <option value="teal" {% if organization_info.get('chat_color_theme') == 'teal' %}selected{% endif %}>Бирюзовая (по умолч.)</option>
2090
- <option value="blue" {% if organization_info.get('chat_color_theme') == 'blue' %}selected{% endif %}>Синяя</option>
2091
- <option value="green" {% if organization_info.get('chat_color_theme') == 'green' %}selected{% endif %}>Зеленая</option>
2092
- <option value="purple" {% if organization_info.get('chat_color_theme') == 'purple' %}selected{% endif %}>Фиолетовая</option>
2093
- </select>
2094
-
2095
- <h3 style="margin-top:20px;">Информация для ИИ-ассистента</h3>
2096
- <label for="about_us">О нас:</label>
2097
  <textarea id="about_us" name="about_us" rows="4">{{ organization_info.get('about_us', '') }}</textarea>
2098
- <label for="shipping">Информация о доставке:</label>
2099
  <textarea id="shipping" name="shipping" rows="4">{{ organization_info.get('shipping', '') }}</textarea>
2100
- <label for="returns">Информация о возврате/обмене:</label>
2101
  <textarea id="returns" name="returns" rows="4">{{ organization_info.get('returns', '') }}</textarea>
2102
- <label for="contact">Контактная информация:</label>
2103
  <textarea id="contact" name="contact" rows="4">{{ organization_info.get('contact', '') }}</textarea>
2104
- <button type="submit" class="add-button"><i class="fas fa-save"></i> Сохранить все настройки</button>
2105
  </form>
 
2106
  </div>
2107
  </details>
2108
  </div>
@@ -2118,7 +2212,7 @@ ADMIN_TEMPLATE = '''
2118
  <input type="hidden" name="action" value="add_product">
2119
  <label for="add_name">Название товара *:</label>
2120
  <input type="text" id="add_name" name="name" required>
2121
- <label for="add_price">Цена ({{ organization_info.get('currency_code', 'KGS') }}) *:</label>
2122
  <input type="number" id="add_price" name="price" step="0.01" min="0" required>
2123
  <label for="add_photos">Фотографии (до 10 шт.):</label>
2124
  <input type="file" id="add_photos" name="photos" accept="image/*" multiple>
@@ -2190,7 +2284,7 @@ ADMIN_TEMPLATE = '''
2190
  {% endif %}
2191
  </h3>
2192
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
2193
- <p><strong>Цена:</strong> {{ "%.2f"|format(product.price) }} {{ organization_info.get('currency_code', 'KGS') }}</p>
2194
  <p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>
2195
  {% set colors = product.get('colors', []) %}
2196
  <p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
@@ -2216,7 +2310,7 @@ ADMIN_TEMPLATE = '''
2216
  <input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}">
2217
  <label>Название *:</label>
2218
  <input type="text" name="name" value="{{ product['name'] }}" required>
2219
- <label>Цена ({{ organization_info.get('currency_code', 'KGS') }}) *:</label>
2220
  <input type="number" name="price" step="0.01" min="0" value="{{ product['price'] }}" required>
2221
  <label>Заменить фотографии (выберите новые файлы, до 10 шт.):</label>
2222
  <input type="file" id="edit_photos_{{ loop.index0 }}" name="photos" accept="image/*" multiple>
@@ -2464,7 +2558,26 @@ def create_environment():
2464
  if new_id not in all_data:
2465
  break
2466
 
2467
- get_env_data(new_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2468
  flash(f'Новая среда с ID {new_id} успешно создана.', 'success')
2469
  return redirect(url_for('admhosto'))
2470
 
@@ -2483,8 +2596,7 @@ def delete_environment(env_id):
2483
  def catalog(env_id):
2484
  data = get_env_data(env_id)
2485
  all_products_raw = data.get('products', [])
2486
- org_info = data.get('organization_info', {})
2487
- currency_code = org_info.get('currency_code', 'KGS')
2488
 
2489
  product_categories = set(p.get('category', 'Без категории') for p in all_products_raw)
2490
  admin_categories = set(data.get('categories', []))
@@ -2503,13 +2615,17 @@ def catalog(env_id):
2503
 
2504
  ordered_categories = [cat for cat in all_cat_names if products_by_category.get(cat)]
2505
 
 
 
2506
  return render_template_string(
2507
  CATALOG_TEMPLATE,
2508
  products_by_category=products_by_category,
2509
  ordered_categories=ordered_categories,
2510
  products_json=json.dumps(products_sorted_for_js),
2511
  repo_id=REPO_ID,
2512
- currency_code=currency_code,
 
 
2513
  env_id=env_id
2514
  )
2515
 
@@ -2517,8 +2633,7 @@ def catalog(env_id):
2517
  def product_detail(env_id, index):
2518
  data = get_env_data(env_id)
2519
  all_products_raw = data.get('products', [])
2520
- org_info = data.get('organization_info', {})
2521
- currency_code = org_info.get('currency_code', 'KGS')
2522
  products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2523
  products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
2524
 
@@ -2531,7 +2646,7 @@ def product_detail(env_id, index):
2531
  PRODUCT_DETAIL_TEMPLATE,
2532
  product=product,
2533
  repo_id=REPO_ID,
2534
- currency_code=currency_code,
2535
  env_id=env_id
2536
  )
2537
 
@@ -2595,16 +2710,13 @@ def create_order(env_id):
2595
  def view_order(env_id, order_id):
2596
  data = get_env_data(env_id)
2597
  order = data.get('orders', {}).get(order_id)
2598
- org_info = data.get('organization_info', {})
2599
- currency_code = org_info.get('currency_code', 'KGS')
2600
- whatsapp_number = org_info.get('whatsapp_number', DEFAULT_WHATSAPP_NUMBER)
2601
-
2602
-
2603
  return render_template_string(ORDER_TEMPLATE,
2604
  order=order,
2605
  repo_id=REPO_ID,
2606
- currency_code=currency_code,
2607
- whatsapp_number=whatsapp_number,
2608
  env_id=env_id)
2609
 
2610
 
@@ -2615,6 +2727,7 @@ def admin(env_id):
2615
  categories = data.get('categories', [])
2616
  organization_info = data.get('organization_info', {})
2617
  chats = data.get('chats', {})
 
2618
 
2619
  if 'orders' not in data or not isinstance(data.get('orders'), dict):
2620
  data['orders'] = {}
@@ -2652,48 +2765,55 @@ def admin(env_id):
2652
  flash(f"Не удалось удалить категорию '{category_to_delete}'.", 'error')
2653
 
2654
  elif action == 'update_org_info':
2655
- organization_info['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
2656
- organization_info['currency_code'] = request.form.get('currency_code', 'KGS')
2657
- organization_info['chat_name'] = request.form.get('chat_name', '').strip()
2658
- organization_info['chat_color_theme'] = request.form.get('chat_color_theme', 'teal')
2659
  organization_info['about_us'] = request.form.get('about_us', '').strip()
2660
  organization_info['shipping'] = request.form.get('shipping', '').strip()
2661
  organization_info['returns'] = request.form.get('returns', '').strip()
2662
  organization_info['contact'] = request.form.get('contact', '').strip()
2663
-
2664
- chat_avatar_file = request.files.get('chat_avatar')
2665
- if chat_avatar_file and chat_avatar_file.filename and HF_TOKEN_WRITE:
2666
- try:
2667
- api = HfApi()
2668
- ext = os.path.splitext(chat_avatar_file.filename)[1].lower()
2669
- if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
2670
- flash(f"Файл аватара не является изображением.", "error")
2671
- else:
2672
- avatar_filename = f"avatar_{env_id}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}"
 
 
 
 
 
2673
 
2674
- old_avatar = organization_info.get('chat_avatar')
2675
  if old_avatar:
2676
  try:
2677
  api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
2678
- except HfHubHTTPError:
2679
  pass
2680
 
 
 
 
 
 
 
 
 
2681
  api.upload_file(
2682
- path_or_fileobj=chat_avatar_file,
2683
  path_in_repo=f"avatars/{avatar_filename}",
2684
- repo_id=REPO_ID,
2685
- repo_type="dataset",
2686
- token=HF_TOKEN_WRITE,
2687
- commit_message=f"Update chat avatar for env {env_id}"
2688
  )
2689
- organization_info['chat_avatar'] = avatar_filename
2690
- flash("Аватар чата успешно обновлен.", "success")
2691
- except Exception as e:
2692
- flash(f"Ошибка при загрузке аватара: {e}", "error")
2693
- elif chat_avatar_file and chat_avatar_file.filename and not HF_TOKEN_WRITE:
2694
- flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
2695
-
2696
- data['organization_info'] = organization_info
 
2697
  save_env_data(env_id, data)
2698
  flash("Настройки магазина и чата успешно обновлены.", 'success')
2699
 
@@ -2913,6 +3033,9 @@ def admin(env_id):
2913
  display_categories = sorted(data.get('categories', []))
2914
  display_organization_info = data.get('organization_info', {})
2915
  display_chats = data.get('chats', {})
 
 
 
2916
 
2917
  return render_template_string(
2918
  ADMIN_TEMPLATE,
@@ -2920,8 +3043,12 @@ def admin(env_id):
2920
  categories=display_categories,
2921
  organization_info=display_organization_info,
2922
  chats=display_chats,
 
2923
  repo_id=REPO_ID,
 
 
2924
  currencies=CURRENCIES,
 
2925
  env_id=env_id
2926
  )
2927
 
@@ -2977,31 +3104,20 @@ def handle_chat_with_ai(env_id):
2977
  def chat_page(env_id):
2978
  data = get_env_data(env_id)
2979
  all_products_raw = data.get('products', [])
2980
- org_info = data.get('organization_info', {})
2981
- currency_code = org_info.get('currency_code', 'KGS')
2982
- chat_name = org_info.get('chat_name', 'Чат с EVA')
2983
- chat_avatar = org_info.get('chat_avatar')
2984
- chat_color_theme = org_info.get('chat_color_theme', 'teal')
2985
-
2986
- color_schemes = {
2987
- "teal": {'bg-dark': '#003C43', 'bg-medium': '#135D66', 'accent': '#48D1CC', 'accent-hover': '#77E4D8'},
2988
- "blue": {'bg-dark': '#012A4A', 'bg-medium': '#013A63', 'accent': '#2A6F97', 'accent-hover': '#468FAF'},
2989
- "green": {'bg-dark': '#14452F', 'bg-medium': '#1A593E', 'accent': '#2A9161', 'accent-hover': '#3BB87B'},
2990
- "purple": {'bg-dark': '#3C096C', 'bg-medium': '#5A189A', 'accent': '#9D4EDD', 'accent-hover': '#C77DFF'}
2991
- }
2992
-
2993
  products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2994
  products_sorted_for_js = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
2995
 
 
 
2996
  return render_template_string(
2997
  CHAT_TEMPLATE,
2998
  products_json=json.dumps(products_sorted_for_js),
2999
  repo_id=REPO_ID,
3000
- currency_code=currency_code,
3001
- env_id=env_id,
3002
- chat_name=chat_name,
3003
- chat_avatar=chat_avatar,
3004
- chat_colors=color_schemes.get(chat_color_theme, color_schemes['teal'])
3005
  )
3006
 
3007
  @app.route('/<env_id>/get_chat/<chat_id>')
 
34
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
35
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
36
 
37
+ DOWNLOAD_RETRIES = 3
38
+ DOWNLOAD_DELAY = 5
39
 
40
  CURRENCIES = {
41
  'KGS': 'Кыргызский сом',
 
46
  'EUR': 'Евро'
47
  }
48
 
49
+ COLOR_SCHEMES = {
50
+ 'default': 'Бирюзовый (по умолч.)',
51
+ 'forest': 'Лесной зеленый',
52
+ 'ocean': 'Глубокий синий',
53
+ 'sunset': 'Закатный оранжевый'
54
+ }
55
+
56
 
57
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
58
 
 
173
  "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров. Мы предлагаем широкий ассортимент продукции, от электроники до товаров для дома, всегда стремясь к качеству и доступности. Наша миссия — сделать ваш шопинг приятным и удобным, предлагая только лучшие товары, тщательно отобранные для вас.",
174
  "shipping": "Доставка осуществляется по всему Кыргызстану. Стоимость и сроки доставки зависят от региона и веса товара. По Бишкеку доставка возможна в течение 1-2 рабочих дней, в регионы — от 3 до 7 дней. Для уточнения деталей свяжитесь с нами.",
175
  "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
176
+ "contact": f"Наш магазин находится по адресу: Рынок Кербен, 6 ряд , 43 контейнер. Связаться с нами можно по телефону или через WhatsApp. Мы работаем ежедневно с 9:00 до 18:00."
177
+ }
178
+ default_settings = {
179
+ "whatsapp_number": "+996701202013",
180
  "currency_code": "KGS",
181
+ "chat_name": "EVA",
182
  "chat_avatar": None,
183
+ "color_scheme": "default"
184
  }
185
+
186
+ env_data = all_data.get(env_id, {})
187
+ if not env_data:
188
+ env_data = {
189
+ 'products': [], 'categories': [], 'orders': {}, 'chats': {},
190
+ 'organization_info': default_organization_info,
191
+ 'settings': default_settings
192
+ }
193
 
194
  if 'products' not in env_data: env_data['products'] = []
195
  if 'categories' not in env_data: env_data['categories'] = []
196
  if 'orders' not in env_data: env_data['orders'] = {}
197
+ if 'organization_info' not in env_data: env_data['organization_info'] = default_organization_info
 
 
 
 
198
  if 'chats' not in env_data: env_data['chats'] = {}
199
+ if 'settings' not in env_data: env_data['settings'] = default_settings
200
+
201
+ settings_changed = False
202
+ for key, value in default_settings.items():
203
+ if key not in env_data['settings']:
204
+ env_data['settings'][key] = value
205
+ settings_changed = True
206
 
207
  products_changed = False
208
  for product in env_data['products']:
 
210
  product['product_id'] = uuid4().hex
211
  products_changed = True
212
 
213
+ if products_changed or settings_changed:
214
  save_env_data(env_id, env_data)
215
 
216
  return env_data
217
 
218
+
219
  def save_env_data(env_id, env_data):
220
  all_data = load_data()
221
  all_data[env_id] = env_data
 
295
  products = data.get('products', [])
296
  categories = data.get('categories', [])
297
  organization_info = data.get('organization_info', {})
298
+ settings = data.get('settings', {})
299
+ currency_code = settings.get('currency_code', 'KGS')
300
+ chat_name = settings.get('chat_name', 'EVA')
301
+
302
  product_info_list = []
303
  for p in products:
304
  if p.get('in_stock', True):
 
477
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
478
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
479
  <style>
480
+ {% if settings.color_scheme == 'forest' %}
481
+ :root {
482
+ --bg-dark: #2F4F4F;
483
+ --bg-medium: #556B2F;
484
+ --accent: #90EE90;
485
+ --accent-hover: #98FB98;
486
+ --text-light: #F5F5DC;
487
+ --text-dark: #333;
488
+ --danger: #CD5C5C;
489
+ --danger-hover: #F08080;
490
+ }
491
+ {% elif settings.color_scheme == 'ocean' %}
492
+ :root {
493
+ --bg-dark: #000080;
494
+ --bg-medium: #1E90FF;
495
+ --accent: #87CEEB;
496
+ --accent-hover: #ADD8E6;
497
+ --text-light: #F0F8FF;
498
+ --text-dark: #333;
499
+ --danger: #FF6347;
500
+ --danger-hover: #FF4500;
501
+ }
502
+ {% elif settings.color_scheme == 'sunset' %}
503
+ :root {
504
+ --bg-dark: #8B4513;
505
+ --bg-medium: #D2691E;
506
+ --accent: #FFA500;
507
+ --accent-hover: #FFD700;
508
+ --text-light: #FFF8DC;
509
+ --text-dark: #333;
510
+ --danger: #DC143C;
511
+ --danger-hover: #FF0000;
512
+ }
513
+ {% else %}
514
  :root {
515
  --bg-dark: #003C43;
516
  --bg-medium: #135D66;
 
521
  --danger: #E57373;
522
  --danger-hover: #EF5350;
523
  }
524
+ {% endif %}
525
+
526
  * { margin: 0; padding: 0; box-sizing: border-box; }
527
  html { -webkit-tap-highlight-color: transparent; }
528
  body {
 
742
  .chat-product-card-actions { display: flex; flex-direction: column; gap: 5px; }
743
  .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%; }
744
  .chat-product-link:hover, .chat-add-to-cart:hover { background-color: #B2DFDB; }
 
745
 
746
  </style>
747
  </head>
 
749
  <div class="container">
750
  <div class="top-bar">
751
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="logo">
752
+ <img src="{{ chat_avatar_url }}" alt="Logo">
753
  </a>
754
  <div class="search-wrapper">
755
  <i class="fas fa-search"></i>
 
1208
  <head>
1209
  <meta charset="UTF-8">
1210
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
1211
+ <title>Gippo312 - Чат с {{ settings.chat_name }}</title>
1212
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
1213
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
1214
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
1215
  <style>
1216
+ {% if settings.color_scheme == 'forest' %}
1217
+ :root {
1218
+ --bg-dark: #2F4F4F;
1219
+ --bg-medium: #556B2F;
1220
+ --accent: #90EE90;
1221
+ --accent-hover: #98FB98;
1222
+ --text-light: #F5F5DC;
1223
+ --text-dark: #333;
1224
+ --danger: #CD5C5C;
1225
+ --danger-hover: #F08080;
1226
+ --chat-bg: #F5F5DC;
1227
+ }
1228
+ {% elif settings.color_scheme == 'ocean' %}
1229
  :root {
1230
+ --bg-dark: #000080;
1231
+ --bg-medium: #1E90FF;
1232
+ --accent: #87CEEB;
1233
+ --accent-hover: #ADD8E6;
1234
+ --text-light: #F0F8FF;
1235
+ --text-dark: #333;
1236
+ --danger: #FF6347;
1237
+ --danger-hover: #FF4500;
1238
+ --chat-bg: #F0F8FF;
1239
+ }
1240
+ {% elif settings.color_scheme == 'sunset' %}
1241
+ :root {
1242
+ --bg-dark: #8B4513;
1243
+ --bg-medium: #D2691E;
1244
+ --accent: #FFA500;
1245
+ --accent-hover: #FFD700;
1246
+ --text-light: #FFF8DC;
1247
+ --text-dark: #333;
1248
+ --danger: #DC143C;
1249
+ --danger-hover: #FF0000;
1250
+ --chat-bg: #FFF8DC;
1251
+ }
1252
+ {% else %}
1253
+ :root {
1254
+ --bg-dark: #003C43;
1255
+ --bg-medium: #135D66;
1256
+ --accent: #48D1CC;
1257
+ --accent-hover: #77E4D8;
1258
  --text-light: #E3FEF7;
1259
  --text-dark: #333;
1260
  --danger: #E57373;
1261
  --danger-hover: #EF5350;
1262
  --chat-bg: #f0f2f5;
1263
  }
1264
+ {% endif %}
1265
+
1266
  * { margin: 0; padding: 0; box-sizing: border-box; }
1267
  html { -webkit-tap-highlight-color: transparent; height: 100%; }
1268
  body {
 
1293
  flex-shrink: 0;
1294
  }
1295
  .chat-header a { color: var(--text-light); font-size: 1.2rem; text-decoration: none; }
1296
+ .chat-header .logo { width: 40px; height: 40px; border-radius: 50%; margin: 0 15px; border: 2px solid var(--accent); }
1297
  .chat-header h1 { font-size: 1.2rem; font-weight: 600; }
1298
  #chat-messages {
1299
  flex-grow: 1;
 
1412
  <div class="chat-container">
1413
  <div class="chat-header">
1414
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
1415
+ <img src="{{ chat_avatar_url }}" alt="Logo" class="logo">
1416
+ <h1>Чат с {{ settings.chat_name }}</h1>
 
 
 
 
1417
  </div>
1418
  <div id="chat-messages"></div>
1419
  <div class="chat-input-container">
 
1736
  window.addEventListener('click', e => { if (e.target.classList.contains('modal')) closeModal(e.target.id); });
1737
  window.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal').forEach(m => closeModal(m.id)); });
1738
 
1739
+ addMessageToChatUI('👋 Здравствуйте! Я ваш виртуальный помощник {{ settings.chat_name }}. Чем могу помочь?', 'ai');
1740
+ chatHistory.push({ role: 'ai', text: 'Здравствуйте! Я ваш виртуальный помощник {{ settings.chat_name }}. Чем могу помочь?' });
 
1741
  });
1742
  </script>
1743
  </body>
 
1930
  return;
1931
  }
1932
  const orderId = document.getElementById('orderId').textContent;
1933
+ const whatsappNumber = "{{ whatsapp_number }}".replace(/[^0-9]/g, '');
1934
  let message = `Здравствуйте! Хочу подтвердить или изменить свой заказ на Gippo312:%0A%0A`;
1935
  message += `*Номер заказа:* ${orderId}%0A%0A`;
1936
 
 
2050
  .chat-message .bubble { display: inline-block; padding: 8px 12px; border-radius: 15px; max-width: 80%; }
2051
  .chat-message.user .bubble { background-color: #dcf8c6; }
2052
  .chat-message.ai .bubble { background-color: #f1f1f1; }
2053
+ .current-avatar { max-width: 60px; max-height: 60px; border-radius: 50%; vertical-align: middle; margin-left: 10px; border: 2px solid var(--bg-medium);}
2054
  </style>
2055
  </head>
2056
  <body>
2057
  <div class="container">
2058
  <div class="header">
2059
  <div class="logo-title-container" style="display: flex; align-items: center; gap: 15px;">
2060
+ <img src="{{ chat_avatar_url }}" alt="Gippo312 Logo">
2061
  <h1><i class="fas fa-tools"></i> Админ-панель Gippo312 (Среда: {{ env_id }})</h1>
2062
  </div>
2063
  <a href="{{ url_for('catalog', env_id=env_id) }}" class="button" style="background-color: var(--bg-medium); color: white;"><i class="fas fa-store"></i> Перейти в каталог</a>
 
2083
  </div>
2084
  <p style="font-size: 0.85rem; color: #999;">Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.</p>
2085
  </div>
2086
+
2087
+ <div class="section">
2088
+ <h2><i class="fas fa-cog"></i> Настройки магазина и чата</h2>
2089
+ <details>
2090
+ <summary><i class="fas fa-chevron-down"></i> Развернуть/Свернуть</summary>
2091
+ <div class="form-content">
2092
+ <form method="POST" enctype="multipart/form-data">
2093
+ <input type="hidden" name="action" value="update_settings">
2094
+
2095
+ <label for="whatsapp_number">Номер WhatsApp для заказов:</label>
2096
+ <input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ settings.whatsapp_number }}" placeholder="+996XXXXXXXXX">
2097
+
2098
+ <label for="currency_code">Валюта магазина:</label>
2099
+ <select id="currency_code" name="currency_code">
2100
+ {% for code, name in currencies.items() %}
2101
+ <option value="{{ code }}" {% if settings.currency_code == code %}selected{% endif %}>{{ name }} ({{ code }})</option>
2102
+ {% endfor %}
2103
+ </select>
2104
+
2105
+ <label for="chat_name">Имя чат-ассистента:</label>
2106
+ <input type="text" id="chat_name" name="chat_name" value="{{ settings.chat_name }}">
2107
+
2108
+ <label for="chat_avatar">Аватар чата (загрузите новый, чтобы изменить):</label>
2109
+ <input type="file" id="chat_avatar" name="chat_avatar" accept="image/png, image/jpeg, image/gif, image/webp">
2110
+ {% if settings.chat_avatar %}
2111
+ <p style="font-size: 0.85rem; margin-top: 5px;">Текущий аватар: <img src="{{ chat_avatar_url }}" class="current-avatar"></p>
2112
+ {% endif %}
2113
+
2114
+ <label for="color_scheme">Цветовая схема каталога и чата:</label>
2115
+ <select id="color_scheme" name="color_scheme">
2116
+ {% for key, name in color_schemes.items() %}
2117
+ <option value="{{ key }}" {% if settings.color_scheme == key %}selected{% endif %}>{{ name }}</option>
2118
+ {% endfor %}
2119
+ </select>
2120
+
2121
+ <button type="submit" class="add-button"><i class="fas fa-save"></i> Сохранить настройки</button>
2122
+ </form>
2123
+ </div>
2124
+ </details>
2125
+ </div>
2126
+
2127
 
2128
  <div class="section">
2129
+ <h2><i class="fas fa-comments"></i> Диалоги с {{ settings.chat_name }}</h2>
2130
  <div class="item-list">
2131
  {% if chats %}
2132
  {% for chat_id, chat_data in chats.items()|sort(reverse=True) %}
 
2180
 
2181
  <div class="flex-item">
2182
  <div class="section">
2183
+ <h2><i class="fas fa-info-circle"></i> Информация о магазине</h2>
2184
+ <details>
2185
  <summary><i class="fas fa-chevron-down"></i> Развернуть/Свернуть</summary>
2186
  <div class="form-content">
2187
+ <form method="POST">
2188
  <input type="hidden" name="action" value="update_org_info">
2189
+ <label for="about_us">О нас (для ИИ-ассистента):</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2190
  <textarea id="about_us" name="about_us" rows="4">{{ organization_info.get('about_us', '') }}</textarea>
2191
+ <label for="shipping">Информация о доставке (для ИИ-ассистента):</label>
2192
  <textarea id="shipping" name="shipping" rows="4">{{ organization_info.get('shipping', '') }}</textarea>
2193
+ <label for="returns">Информация о возврате/обмене (для ИИ-ассистента):</label>
2194
  <textarea id="returns" name="returns" rows="4">{{ organization_info.get('returns', '') }}</textarea>
2195
+ <label for="contact">Контактная информация (для ИИ-ассистента):</label>
2196
  <textarea id="contact" name="contact" rows="4">{{ organization_info.get('contact', '') }}</textarea>
2197
+ <button type="submit" class="add-button"><i class="fas fa-save"></i> Сохранить</button>
2198
  </form>
2199
+ <p style="font-size: 0.85rem; color: #999;">Эта информация будет использоваться ИИ-ассистентом для ответов на вопросы о вашем магазине.</p>
2200
  </div>
2201
  </details>
2202
  </div>
 
2212
  <input type="hidden" name="action" value="add_product">
2213
  <label for="add_name">Название товара *:</label>
2214
  <input type="text" id="add_name" name="name" required>
2215
+ <label for="add_price">Цена ({{ currency_code }}) *:</label>
2216
  <input type="number" id="add_price" name="price" step="0.01" min="0" required>
2217
  <label for="add_photos">Фотографии (до 10 шт.):</label>
2218
  <input type="file" id="add_photos" name="photos" accept="image/*" multiple>
 
2284
  {% endif %}
2285
  </h3>
2286
  <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
2287
+ <p><strong>Цена:</strong> {{ "%.2f"|format(product.price) }} {{ currency_code }}</p>
2288
  <p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>
2289
  {% set colors = product.get('colors', []) %}
2290
  <p><strong>Цвета/Вар-ты:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>
 
2310
  <input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}">
2311
  <label>Название *:</label>
2312
  <input type="text" name="name" value="{{ product['name'] }}" required>
2313
+ <label>Цена ({{ currency_code }}) *:</label>
2314
  <input type="number" name="price" step="0.01" min="0" value="{{ product['price'] }}" required>
2315
  <label>Заменить фотографии (выберите новые файлы, до 10 шт.):</label>
2316
  <input type="file" id="edit_photos_{{ loop.index0 }}" name="photos" accept="image/*" multiple>
 
2558
  if new_id not in all_data:
2559
  break
2560
 
2561
+ all_data[new_id] = {
2562
+ 'products': [],
2563
+ 'categories': [],
2564
+ 'orders': {},
2565
+ 'organization_info': {
2566
+ "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров.",
2567
+ "shipping": "Доставка осуществляется по всему Кыргызстану.",
2568
+ "returns": "Возврат и обмен товара возможен в течение 14 дней.",
2569
+ "contact": "Наш магазин находится по адресу: ... Связаться с нами можно по телефону ..."
2570
+ },
2571
+ 'settings': {
2572
+ "whatsapp_number": "+996701202013",
2573
+ "currency_code": "KGS",
2574
+ "chat_name": "EVA",
2575
+ "chat_avatar": None,
2576
+ "color_scheme": "default"
2577
+ },
2578
+ 'chats': {}
2579
+ }
2580
+ save_data(all_data)
2581
  flash(f'Новая среда с ID {new_id} успешно создана.', 'success')
2582
  return redirect(url_for('admhosto'))
2583
 
 
2596
  def catalog(env_id):
2597
  data = get_env_data(env_id)
2598
  all_products_raw = data.get('products', [])
2599
+ settings = data.get('settings', {})
 
2600
 
2601
  product_categories = set(p.get('category', 'Без категории') for p in all_products_raw)
2602
  admin_categories = set(data.get('categories', []))
 
2615
 
2616
  ordered_categories = [cat for cat in all_cat_names if products_by_category.get(cat)]
2617
 
2618
+ chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{settings['chat_avatar']}" if settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
2619
+
2620
  return render_template_string(
2621
  CATALOG_TEMPLATE,
2622
  products_by_category=products_by_category,
2623
  ordered_categories=ordered_categories,
2624
  products_json=json.dumps(products_sorted_for_js),
2625
  repo_id=REPO_ID,
2626
+ currency_code=settings.get('currency_code', 'KGS'),
2627
+ settings=settings,
2628
+ chat_avatar_url=chat_avatar_url,
2629
  env_id=env_id
2630
  )
2631
 
 
2633
  def product_detail(env_id, index):
2634
  data = get_env_data(env_id)
2635
  all_products_raw = data.get('products', [])
2636
+ settings = data.get('settings', {})
 
2637
  products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
2638
  products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
2639
 
 
2646
  PRODUCT_DETAIL_TEMPLATE,
2647
  product=product,
2648
  repo_id=REPO_ID,
2649
+ currency_code=settings.get('currency_code', 'KGS'),
2650
  env_id=env_id
2651
  )
2652
 
 
2710
  def view_order(env_id, order_id):
2711
  data = get_env_data(env_id)
2712
  order = data.get('orders', {}).get(order_id)
2713
+ settings = data.get('settings', {})
2714
+
 
 
 
2715
  return render_template_string(ORDER_TEMPLATE,
2716
  order=order,
2717
  repo_id=REPO_ID,
2718
+ currency_code=settings.get('currency_code', 'KGS'),
2719
+ whatsapp_number=settings.get('whatsapp_number', ''),
2720
  env_id=env_id)
2721
 
2722
 
 
2727
  categories = data.get('categories', [])
2728
  organization_info = data.get('organization_info', {})
2729
  chats = data.get('chats', {})
2730
+ settings = data.get('settings', {})
2731
 
2732
  if 'orders' not in data or not isinstance(data.get('orders'), dict):
2733
  data['orders'] = {}
 
2765
  flash(f"Не удалось удалить категорию '{category_to_delete}'.", 'error')
2766
 
2767
  elif action == 'update_org_info':
 
 
 
 
2768
  organization_info['about_us'] = request.form.get('about_us', '').strip()
2769
  organization_info['shipping'] = request.form.get('shipping', '').strip()
2770
  organization_info['returns'] = request.form.get('returns', '').strip()
2771
  organization_info['contact'] = request.form.get('contact', '').strip()
2772
+ data['organization_info'] = organization_info
2773
+ save_env_data(env_id, data)
2774
+ flash("Информация о магазине успешно обновлена.", 'success')
2775
+
2776
+ elif action == 'update_settings':
2777
+ settings['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
2778
+ settings['currency_code'] = request.form.get('currency_code', 'KGS')
2779
+ settings['chat_name'] = request.form.get('chat_name', 'EVA').strip()
2780
+ settings['color_scheme'] = request.form.get('color_scheme', 'default')
2781
+
2782
+ avatar_file = request.files.get('chat_avatar')
2783
+ if avatar_file and avatar_file.filename:
2784
+ if HF_TOKEN_WRITE:
2785
+ try:
2786
+ api = HfApi()
2787
 
2788
+ old_avatar = settings.get('chat_avatar')
2789
  if old_avatar:
2790
  try:
2791
  api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE)
2792
+ except Exception:
2793
  pass
2794
 
2795
+ ext = os.path.splitext(avatar_file.filename)[1].lower()
2796
+ avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}"
2797
+
2798
+ uploads_dir = 'uploads_temp'
2799
+ os.makedirs(uploads_dir, exist_ok=True)
2800
+ temp_path = os.path.join(uploads_dir, avatar_filename)
2801
+ avatar_file.save(temp_path)
2802
+
2803
  api.upload_file(
2804
+ path_or_fileobj=temp_path,
2805
  path_in_repo=f"avatars/{avatar_filename}",
2806
+ repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE
 
 
 
2807
  )
2808
+ settings['chat_avatar'] = avatar_filename
2809
+ os.remove(temp_path)
2810
+ flash("Аватар чата успешно обновлен.", 'success')
2811
+ except Exception as e:
2812
+ flash(f"Ошибка при загрузке аватара: {e}", 'error')
2813
+ else:
2814
+ flash("HF_TOKEN (write) не настроен. Аватар не был загружен.", "warning")
2815
+
2816
+ data['settings'] = settings
2817
  save_env_data(env_id, data)
2818
  flash("Настройки магазина и чата успешно обновлены.", 'success')
2819
 
 
3033
  display_categories = sorted(data.get('categories', []))
3034
  display_organization_info = data.get('organization_info', {})
3035
  display_chats = data.get('chats', {})
3036
+ display_settings = data.get('settings', {})
3037
+
3038
+ chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{display_settings['chat_avatar']}" if display_settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
3039
 
3040
  return render_template_string(
3041
  ADMIN_TEMPLATE,
 
3043
  categories=display_categories,
3044
  organization_info=display_organization_info,
3045
  chats=display_chats,
3046
+ settings=display_settings,
3047
  repo_id=REPO_ID,
3048
+ currency_code=display_settings.get('currency_code', 'KGS'),
3049
+ chat_avatar_url=chat_avatar_url,
3050
  currencies=CURRENCIES,
3051
+ color_schemes=COLOR_SCHEMES,
3052
  env_id=env_id
3053
  )
3054
 
 
3104
  def chat_page(env_id):
3105
  data = get_env_data(env_id)
3106
  all_products_raw = data.get('products', [])
3107
+ settings = data.get('settings', {})
 
 
 
 
 
 
 
 
 
 
 
 
3108
  products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
3109
  products_sorted_for_js = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
3110
 
3111
+ chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{settings['chat_avatar']}" if settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png"
3112
+
3113
  return render_template_string(
3114
  CHAT_TEMPLATE,
3115
  products_json=json.dumps(products_sorted_for_js),
3116
  repo_id=REPO_ID,
3117
+ currency_code=settings.get('currency_code', 'KGS'),
3118
+ settings=settings,
3119
+ chat_avatar_url=chat_avatar_url,
3120
+ env_id=env_id
 
3121
  )
3122
 
3123
  @app.route('/<env_id>/get_chat/<chat_id>')