Kgshop commited on
Commit
ae8a640
·
verified ·
1 Parent(s): 9151c0c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -46
app.py CHANGED
@@ -36,8 +36,14 @@ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
36
  STORE_ADDRESS = "Рынок Кербент, 6 ряд , 43 контейнер "
37
  WHATSAPP_NUMBER = "+996701202013"
38
 
39
- CURRENCY_CODE = 'KGS'
40
- CURRENCY_NAME = 'Кыргызский сом'
 
 
 
 
 
 
41
 
42
  DOWNLOAD_RETRIES = 3
43
  DOWNLOAD_DELAY = 5
@@ -145,14 +151,19 @@ def save_main_data(data):
145
  def load_env_data(env_id):
146
  env_file = get_env_data_path(env_id)
147
  default_organization_info = {
148
- "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров. Мы предлагаем широкий ассортимент продукции, от электроники до товаров для дома, всегда стремясь к качеству и доступности. Наша миссия — сделать ваш шопинг приятным и удобным, предлагая только лучшие товары, тщательно отобранные для вас.",
149
- "shipping": "Доставка осуществляется по всему Кыргызстану. Стоимость и сроки доставки зависят от региона и веса товара. По Бишкеку доставка возможна в течение 1-2 рабочих дней, в регионы — от 3 до 7 дней. Для уточнения деталей свяжитесь с нами.",
150
- "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.",
151
- "contact": f"Наш магазин находится по адресу: {STORE_ADDRESS}. Связаться с нами можно по телефону: {WHATSAPP_NUMBER} или через WhatsApp по этому же номеру. Мы работаем ежедневно с 9:00 до 18:00."
 
 
 
 
 
 
152
  }
153
- default_data = {'products': [], 'categories': [], 'orders': {}, 'organization_info': default_organization_info, 'chats': {}}
154
 
155
- data = default_data
156
  try:
157
  with open(env_file, 'r', encoding='utf-8') as file:
158
  data = json.load(file)
@@ -164,22 +175,24 @@ def load_env_data(env_id):
164
  with open(env_file, 'r', encoding='utf-8') as file:
165
  data = json.load(file)
166
  if not isinstance(data, dict):
167
- data = default_data
168
  except (FileNotFoundError, json.JSONDecodeError):
169
- data = default_data
170
  else:
171
- data = default_data
172
 
173
- if 'products' not in data: data['products'] = []
174
- if 'categories' not in data: data['categories'] = []
175
- if 'orders' not in data: data['orders'] = {}
176
- if 'organization_info' not in data: data['organization_info'] = default_organization_info
177
- if 'chats' not in data: data['chats'] = {}
178
 
 
179
  for product in data['products']:
180
  if 'product_id' not in product:
181
  product['product_id'] = uuid4().hex
182
- if any('product_id' not in p for p in data['products']):
 
183
  save_env_data(env_id, data)
184
 
185
  return data
@@ -256,12 +269,13 @@ def generate_chat_response(env_id, message, chat_history_from_client):
256
  products = data.get('products', [])
257
  categories = data.get('categories', [])
258
  organization_info = data.get('organization_info', {})
 
259
 
260
  product_info_list = []
261
  for p in products:
262
  if p.get('in_stock', True):
263
  price_display = f"{p.get('price', 0):.2f}".replace('.00', '')
264
- product_info_list.append(f"- [ID_ТОВАРА: {p.get('product_id', 'N/A')} Название: {p.get('name', 'Без названия')}], Категория: {p.get('category', 'Без категории')}, Цена: {price_display} {CURRENCY_CODE}, Описание: {p.get('description', '')[:100]}...")
265
  product_list_str = "\n".join(product_info_list) if product_info_list else "В данный момент нет товаров в наличии."
266
  category_list_str = ", ".join(categories) if categories else "Категорий пока нет."
267
 
@@ -714,12 +728,17 @@ CHAT_TEMPLATE = '''
714
  <head>
715
  <meta charset="UTF-8">
716
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
717
- <title>Gippo312 - Чат с EVA</title>
718
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
719
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
720
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
721
  <style>
722
- :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; }
 
 
 
 
 
723
  * { margin: 0; padding: 0; box-sizing: border-box; }
724
  html { -webkit-tap-highlight-color: transparent; height: 100%; }
725
  body { font-family: 'Montserrat', sans-serif; background-color: var(--chat-bg); color: var(--text-dark); display: flex; flex-direction: column; height: 100%; overflow: hidden; }
@@ -780,8 +799,8 @@ CHAT_TEMPLATE = '''
780
  <div class="chat-container">
781
  <div class="chat-header">
782
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
783
- <img src="https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" alt="Logo" class="logo">
784
- <h1>Чат с EVA</h1>
785
  </div>
786
  <div id="chat-messages"></div>
787
  <div class="chat-input-container">
@@ -1017,8 +1036,8 @@ CHAT_TEMPLATE = '''
1017
  updateCartButton();
1018
  window.addEventListener('click', e => { if (e.target.classList.contains('modal')) closeModal(e.target.id); });
1019
  window.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal').forEach(m => closeModal(m.id)); });
1020
- addMessageToChatUI('👋 Здравствуйте! Я ваш виртуальный помощник EVA. Чем могу помочь?', 'ai');
1021
- chatHistory.push({ role: 'ai', text: 'Здравствуйте! Я ваш виртуальный помощник EVA. Чем могу помочь?' });
1022
  });
1023
  </script>
1024
  </body>
@@ -1162,7 +1181,7 @@ ORDER_TEMPLATE = '''
1162
  function sendOrderViaWhatsApp() {
1163
  if (order.cart.length === 0) { alert("Нельзя отправить пустой заказ."); return; }
1164
  const orderId = document.getElementById('orderId').textContent;
1165
- const whatsappNumber = "{{ whatsapp_number }}";
1166
  let message = `Здравству��те! Хочу подтвердить или изменить свой заказ на Gippo312:%0A%0A*Номер заказа:* ${orderId}%0A%0A`;
1167
  order.cart.forEach(item => {
1168
  message += `*${item.name}* ${item.color !== 'N/A' ? `(${item.color})` : ''}%0A`;
@@ -1206,7 +1225,8 @@ ADMIN_TEMPLATE = '''
1206
  .section { margin-bottom: 30px; padding: 20px; background-color: #fdfdff; border: 1px solid #e0e0e0; border-radius: 8px; }
1207
  form { margin-bottom: 20px; }
1208
  label { font-weight: 500; margin-top: 10px; display: block; color: #666; font-size: 0.9rem;}
1209
- input[type="text"], input[type="number"], input[type="password"], input[type="tel"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 5px; border: 1px solid #e0e0e0; border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; background-color: #fff; }
 
1210
  input:focus, textarea:focus, select:focus { border-color: var(--bg-medium); outline: none; box-shadow: 0 0 0 2px rgba(19, 93, 102, 0.1); }
1211
  textarea { min-height: 80px; resize: vertical; }
1212
  input[type="file"] { padding: 8px; background-color: #ffffff; cursor: pointer; border: 1px solid #e0e0e0;}
@@ -1246,6 +1266,8 @@ ADMIN_TEMPLATE = '''
1246
  .download-hf-button:hover { background-color: #5a6268; }
1247
  .flex-container { display: flex; flex-wrap: wrap; gap: 20px; }
1248
  .flex-item { flex: 1; min-width: 350px; }
 
 
1249
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem;}
1250
  .message.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb;}
1251
  .message.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb;}
@@ -1317,15 +1339,35 @@ ADMIN_TEMPLATE = '''
1317
  <div class="flex-item">
1318
  <div class="section">
1319
  <h2><i class="fas fa-info-circle"></i> Информация о магазине</h2>
1320
- <details><summary><i class="fas fa-chevron-down"></i> Развернуть/Свернуть</summary><div class="form-content"><form method="POST"><input type="hidden" name="action" value="update_org_info"><label for="about_us">О нас:</label><textarea id="about_us" name="about_us" rows="4">{{ organization_info.get('about_us', '') }}</textarea><label for="shipping">Доставка:</label><textarea id="shipping" name="shipping" rows="4">{{ organization_info.get('shipping', '') }}</textarea><label for="returns">Возврат/обмен:</label><textarea id="returns" name="returns" rows="4">{{ organization_info.get('returns', '') }}</textarea><label for="contact">Контакты:</label><textarea id="contact" name="contact" rows="4">{{ organization_info.get('contact', '') }}</textarea><button type="submit" class="add-button"><i class="fas fa-save"></i> Сохранить</button></form></div></details>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1321
  </div>
1322
  </div>
1323
  </div>
1324
  <div class="section">
1325
  <h2><i class="fas fa-box-open"></i> Управление товарами</h2>
1326
- <details><summary><i class="fas fa-plus-circle"></i> Добавить новый товар</summary><div class="form-content"><form id="add-product-form" method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_product"><label for="add_name">Название *:</label><input type="text" id="add_name" name="name" required><label for="add_price">Цена ({{ currency_code }}) *:</label><input type="number" id="add_price" name="price" step="0.01" min="0" required><label for="add_photos">Фото (до 10):</label><input type="file" id="add_photos" name="photos" accept="image/*" multiple><label for="add_description">Описание:</label><textarea id="add_description" name="description" rows="4"></textarea><button type="button" class="button ai-generate-button" onclick="generateDescription('add_photos', 'add_description', 'add_gen_lang')"><i class="fas fa-magic"></i> Сгенерировать</button><label for="add_gen_lang">Язык:</label><select id="add_gen_lang" name="gen_lang" style="width: auto; display: inline-block; margin-left: 10px;"><option>Русский</option><option>Кыргызский</option><option>Казахский</option><option>Узбекский</option></select><label for="add_category">Категория:</label><select id="add_category" name="category"><option value="Без категории">Без категории</option>{% for category in categories %}<option value="{{ category }}">{{ category }}</option>{% endfor %}</select><label>Цвета/Варианты:</label><div id="add-color-inputs"><div class="color-input-group"><input type="text" name="colors" placeholder="Розовый"><button type="button" class="remove-color-btn" onclick="removeColorInput(this)"><i class="fas fa-times"></i></button></div></div><button type="button" class="button add-color-btn" style="margin-top: 5px;" onclick="addColorInput('add-color-inputs')"><i class="fas fa-palette"></i> Добавить</button><br><div style="margin-top: 15px;"><input type="checkbox" id="add_in_stock" name="in_stock" checked><label for="add_in_stock" class="inline-label">В наличии</label></div><div style="margin-top: 5px;"><input type="checkbox" id="add_is_top" name="is_top"><label for="add_is_top" class="inline-label">Топ товар</label></div><br><button type="submit" class="add-button" style="margin-top: 20px;"><i class="fas fa-save"></i> Добавить товар</button></form></div></details>
1327
  <h3>Список товаров:</h3>
1328
- {% if products %}<div class="item-list">{% for product in products %}<div class="item"><div style="display: flex; gap: 15px; align-items: flex-start;"><div class="photo-preview" style="flex-shrink: 0;">{% if product.get('photos') %}<a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ env_id }}/{{ product['photos'][0] }}" target="_blank"><img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ env_id }}/{{ product['photos'][0] }}" alt="Фото"></a>{% else %}<img src="https://via.placeholder.com/70x70.png?text=N/A" alt="Нет фото">{% endif %}</div><div style="flex-grow: 1;"><h3 style="margin-top: 0; margin-bottom: 5px; color: var(--text-dark);">{{ product['name'] }}{% if product.get('in_stock', True) %}<span class="status-indicator in-stock">В наличии</span>{% else %}<span class="status-indicator out-of-stock">Нет в наличии</span>{% endif %}{% if product.get('is_top', False) %}<span class="status-indicator top-product"><i class="fas fa-star"></i> Топ</span>{% endif %}</h3><p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p><p><strong>Цена:</strong> {{ "%.2f"|format(product.price) }} {{ currency_code }}</p><p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>{% set colors = product.get('colors', []) %}<p><strong>Цвета:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>{% if product.get('photos') and product['photos']|length > 1 %}<p style="font-size: 0.8rem; color: #999;">(Фото: {{ product['photos']|length }})</p>{% endif %}</div></div><div class="item-actions"><button type="button" class="button" onclick="toggleEditForm('edit-form-{{ loop.index0 }}')"><i class="fas fa-edit"></i> Редактировать</button><form method="POST" style="margin:0;" onsubmit="return confirm('Удалить товар \'{{ product['name'] }}\'?');"><input type="hidden" name="action" value="delete_product"><input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}"><button type="submit" class="delete-button"><i class="fas fa-trash-alt"></i> Удалить</button></form></div><div id="edit-form-{{ loop.index0 }}" class="edit-form-container"><h4><i class="fas fa-edit"></i> Редактирование: {{ product['name'] }}</h4><form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_product"><input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}"><label>Название *:</label><input type="text" name="name" value="{{ product['name'] }}" required><label>Цена ({{ currency_code }}) *:</label><input type="number" name="price" step="0.01" min="0" value="{{ product['price'] }}" required><label>Заменить фото:</label><input type="file" id="edit_photos_{{ loop.index0 }}" name="photos" accept="image/*" multiple>{% if product.get('photos') %}<p style="font-size: 0.85rem; margin-top: 5px;">Текущие:</p><div class="photo-preview">{% for photo in product['photos'] %}<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ env_id }}/{{ photo }}" alt="Фото {{ loop.index }}">{% endfor %}</div>{% endif %}<label>Описание:</label><textarea id="edit_description_{{ loop.index0 }}" name="description" rows="4">{{ product.get('description', '') }}</textarea><button type="button" class="button ai-generate-button" onclick="generateDescription('edit_photos_{{ loop.index0 }}', 'edit_description_{{ loop.index0 }}', 'edit_gen_lang_{{ loop.index0 }}')"><i class="fas fa-magic"></i> Сгенерировать</button><label for="edit_gen_lang_{{ loop.index0 }}">Язык:</label><select id="edit_gen_lang_{{ loop.index0 }}" name="gen_lang" style="width: auto; display: inline-block; margin-left: 10px;"><option>Русский</option><option>Кыргызский</option><option>Казахский</option><option>Узбекский</option></select><label>Категория:</label><select name="category"><option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>{% for category in categories %}<option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>{% endfor %}</select><label>Цвета/Варианты:</label><div id="edit-color-inputs-{{ loop.index0 }}">{% set current_colors = product.get('colors', []) %}{% if current_colors and current_colors|select('ne', '')|list|length > 0 %}{% for color in current_colors %}{% if color.strip() %}<div class="color-input-group"><input type="text" name="colors" value="{{ color }}"><button type="button" class="remove-color-btn" onclick="removeColorInput(this)"><i class="fas fa-times"></i></button></div>{% endif %}{% endfor %}{% else %}<div class="color-input-group"><input type="text" name="colors" placeholder="Цвет"><button type="button" class="remove-color-btn" onclick="removeColorInput(this)"><i class="fas fa-times"></i></button></div>{% endif %}</div><button type="button" class="button add-color-btn" style="margin-top: 5px;" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')"><i class="fas fa-palette"></i> Добавить</button><br><div style="margin-top: 15px;"><input type="checkbox" id="edit_in_stock_{{ loop.index0 }}" name="in_stock" {% if product.get('in_stock', True) %}checked{% endif %}><label for="edit_in_stock_{{ loop.index0 }}" class="inline-label">В наличии</label></div><div style="margin-top: 5px;"><input type="checkbox" id="edit_is_top_{{ loop.index0 }}" name="is_top" {% if product.get('is_top', False) %}checked{% endif %}><label for="edit_is_top_{{ loop.index0 }}" class="inline-label">Топ товар</label></div><br><button type="submit" class="add-button" style="margin-top: 20px;"><i class="fas fa-save"></i> Сохранить</button></form></div></div>{% endfor %}</div>{% else %}<p>Товаров пока нет.</p>{% endif %}
1329
  </div>
1330
  </div>
1331
  <div id="chat-modal" class="modal"><div class="modal-content"><span class="button delete-button" style="float:right;" onclick="closeModal('chat-modal')">&times;</span><h2 id="chat-modal-title">Просмотр диалога</h2><div id="chat-modal-body" class="chat-message-viewer"></div></div></div>
@@ -1471,6 +1513,9 @@ def admhosto():
1471
  def catalog(env_id):
1472
  data = load_env_data(env_id)
1473
  all_products_raw = data.get('products', [])
 
 
 
1474
  product_categories = set(p.get('category', 'Без категории') for p in all_products_raw)
1475
  admin_categories = set(data.get('categories', []))
1476
  all_cat_names = sorted(list(product_categories.union(admin_categories)))
@@ -1485,12 +1530,14 @@ def catalog(env_id):
1485
 
1486
  return render_template_string(
1487
  CATALOG_TEMPLATE, products_by_category=products_by_category, ordered_categories=ordered_categories,
1488
- products_json=json.dumps(products_sorted_for_js), repo_id=REPO_ID, env_id=env_id, currency_code=CURRENCY_CODE
1489
  )
1490
 
1491
  @app.route('/<env_id>/product/<int:index>')
1492
  def product_detail(env_id, index):
1493
  data = load_env_data(env_id)
 
 
1494
  all_products_raw = data.get('products', [])
1495
  products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
1496
  products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
@@ -1498,7 +1545,7 @@ def product_detail(env_id, index):
1498
  product = products_sorted[index]
1499
  except IndexError:
1500
  return "Товар не найден или отсутствует в наличии.", 404
1501
- return render_template_string(PRODUCT_DETAIL_TEMPLATE, product=product, repo_id=REPO_ID, env_id=env_id, currency_code=CURRENCY_CODE)
1502
 
1503
  @app.route('/<env_id>/create_order', methods=['POST'])
1504
  def create_order(env_id):
@@ -1536,8 +1583,11 @@ def create_order(env_id):
1536
  @app.route('/<env_id>/order/<order_id>')
1537
  def view_order(env_id, order_id):
1538
  data = load_env_data(env_id)
 
 
 
1539
  order = data.get('orders', {}).get(order_id)
1540
- return render_template_string(ORDER_TEMPLATE, order=order, env_id=env_id, repo_id=REPO_ID, currency_code=CURRENCY_CODE, whatsapp_number=WHATSAPP_NUMBER)
1541
 
1542
  @app.route('/<env_id>/admin', methods=['GET', 'POST'])
1543
  def admin(env_id):
@@ -1575,6 +1625,12 @@ def admin(env_id):
1575
  organization_info['shipping'] = request.form.get('shipping', '').strip()
1576
  organization_info['returns'] = request.form.get('returns', '').strip()
1577
  organization_info['contact'] = request.form.get('contact', '').strip()
 
 
 
 
 
 
1578
  data['organization_info'] = organization_info
1579
  save_env_data(env_id, data)
1580
  flash("Информация о магазине обновлена.", 'success')
@@ -1592,18 +1648,32 @@ def admin(env_id):
1592
 
1593
  photos_list = []
1594
  photos_files = request.files.getlist('photos')
1595
- if photos_files and any(f.filename for f in photos_files) and HF_TOKEN_WRITE:
1596
- api = HfApi()
1597
- for photo in photos_files[:10]:
1598
- if photo and photo.filename:
1599
- try:
1600
- ext = os.path.splitext(photo.filename)[1].lower()
1601
- if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: continue
1602
- safe_name = secure_filename(name.replace(' ', '_'))[:50]
1603
- photo_filename = f"{safe_name}_{datetime.now().strftime('%f')}{ext}"
1604
- api.upload_file(path_or_fileobj=photo, path_in_repo=f"photos/{env_id}/{photo_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE)
1605
- photos_list.append(photo_filename)
1606
- except Exception: flash(f"Ошибка загрузки фото {photo.filename}.", 'error')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1607
 
1608
  if action == 'add_product':
1609
  new_product = {'product_id': uuid4().hex}
@@ -1656,7 +1726,7 @@ def admin(env_id):
1656
  return render_template_string(
1657
  ADMIN_TEMPLATE, products=sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower()),
1658
  categories=sorted(current_data.get('categories', [])), organization_info=current_data.get('organization_info', {}),
1659
- chats=current_data.get('chats', {}), repo_id=REPO_ID, env_id=env_id, currency_code=CURRENCY_CODE
1660
  )
1661
 
1662
  @app.route('/generate_description_ai', methods=['POST'])
@@ -1692,9 +1762,14 @@ def handle_chat_with_ai(env_id):
1692
  @app.route('/<env_id>/chat')
1693
  def chat_page(env_id):
1694
  data = load_env_data(env_id)
 
 
1695
  products_in_stock = [p for p in data.get('products', []) if p.get('in_stock', True)]
1696
  products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
1697
- return render_template_string(CHAT_TEMPLATE, products_json=json.dumps(products_sorted), repo_id=REPO_ID, env_id=env_id, currency_code=CURRENCY_CODE)
 
 
 
1698
 
1699
  @app.route('/<env_id>/get_chat/<chat_id>')
1700
  def get_chat_history(env_id, chat_id):
 
36
  STORE_ADDRESS = "Рынок Кербент, 6 ряд , 43 контейнер "
37
  WHATSAPP_NUMBER = "+996701202013"
38
 
39
+ CURRENCIES = {
40
+ 'KGS': 'Кыргызский сом',
41
+ 'KZT': 'Казахстанский тенге',
42
+ 'UAH': 'Украинская гривна',
43
+ 'RUB': 'Российский рубль',
44
+ 'USD': 'Доллар США',
45
+ 'EUR': 'Евро'
46
+ }
47
 
48
  DOWNLOAD_RETRIES = 3
49
  DOWNLOAD_DELAY = 5
 
151
  def load_env_data(env_id):
152
  env_file = get_env_data_path(env_id)
153
  default_organization_info = {
154
+ "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров.",
155
+ "shipping": "Доставка осуществляется по всему Кыргызстану.",
156
+ "returns": "Возврат и обмен товара возможен в течение 14 дней.",
157
+ "contact": f"Наш магазин находится по адресу: {STORE_ADDRESS}. Связаться с нами можно по телефону или через WhatsApp.",
158
+ "whatsapp_number": WHATSAPP_NUMBER,
159
+ "currency_code": "KGS",
160
+ "chat_name": "Чат с EVA",
161
+ "chat_avatar_url": "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png",
162
+ "chat_theme_bg": "#003C43",
163
+ "chat_theme_accent": "#48D1CC"
164
  }
 
165
 
166
+ data = {}
167
  try:
168
  with open(env_file, 'r', encoding='utf-8') as file:
169
  data = json.load(file)
 
175
  with open(env_file, 'r', encoding='utf-8') as file:
176
  data = json.load(file)
177
  if not isinstance(data, dict):
178
+ data = {}
179
  except (FileNotFoundError, json.JSONDecodeError):
180
+ data = {}
181
  else:
182
+ data = {}
183
 
184
+ data.setdefault('products', [])
185
+ data.setdefault('categories', [])
186
+ data.setdefault('orders', {})
187
+ data.setdefault('chats', {})
188
+ data.setdefault('organization_info', {}).update({k: v for k, v in default_organization_info.items() if k not in data.get('organization_info', {})})
189
 
190
+ needs_saving = False
191
  for product in data['products']:
192
  if 'product_id' not in product:
193
  product['product_id'] = uuid4().hex
194
+ needs_saving = True
195
+ if needs_saving:
196
  save_env_data(env_id, data)
197
 
198
  return data
 
269
  products = data.get('products', [])
270
  categories = data.get('categories', [])
271
  organization_info = data.get('organization_info', {})
272
+ currency_code = organization_info.get('currency_code', 'KGS')
273
 
274
  product_info_list = []
275
  for p in products:
276
  if p.get('in_stock', True):
277
  price_display = f"{p.get('price', 0):.2f}".replace('.00', '')
278
+ product_info_list.append(f"- [ID_ТОВАРА: {p.get('product_id', 'N/A')} Название: {p.get('name', 'Без названия')}], Категория: {p.get('category', 'Без категории')}, Цена: {price_display} {currency_code}, Описание: {p.get('description', '')[:100]}...")
279
  product_list_str = "\n".join(product_info_list) if product_info_list else "В данный момент нет товаров в наличии."
280
  category_list_str = ", ".join(categories) if categories else "Категорий пока нет."
281
 
 
728
  <head>
729
  <meta charset="UTF-8">
730
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
731
+ <title>{{ organization_info.get('chat_name', 'Gippo312 - Чат') }}</title>
732
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
733
  <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
734
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/10.2.0/swiper-bundle.min.css">
735
  <style>
736
+ :root {
737
+ --bg-dark: {{ organization_info.get('chat_theme_bg', '#003C43') }};
738
+ --accent: {{ organization_info.get('chat_theme_accent', '#48D1CC') }};
739
+ --bg-medium: #135D66;
740
+ --accent-hover: #77E4D8; --text-light: #E3FEF7; --text-dark: #333; --danger: #E57373; --danger-hover: #EF5350; --chat-bg: #f0f2f5;
741
+ }
742
  * { margin: 0; padding: 0; box-sizing: border-box; }
743
  html { -webkit-tap-highlight-color: transparent; height: 100%; }
744
  body { font-family: 'Montserrat', sans-serif; background-color: var(--chat-bg); color: var(--text-dark); display: flex; flex-direction: column; height: 100%; overflow: hidden; }
 
799
  <div class="chat-container">
800
  <div class="chat-header">
801
  <a href="{{ url_for('catalog', env_id=env_id) }}"><i class="fas fa-arrow-left"></i></a>
802
+ <img src="{{ organization_info.get('chat_avatar_url') }}" alt="Logo" class="logo">
803
+ <h1>{{ organization_info.get('chat_name') }}</h1>
804
  </div>
805
  <div id="chat-messages"></div>
806
  <div class="chat-input-container">
 
1036
  updateCartButton();
1037
  window.addEventListener('click', e => { if (e.target.classList.contains('modal')) closeModal(e.target.id); });
1038
  window.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal').forEach(m => closeModal(m.id)); });
1039
+ addMessageToChatUI('👋 Здравствуйте! Я ваш виртуальный помощник. Чем могу помочь?', 'ai');
1040
+ chatHistory.push({ role: 'ai', text: 'Здравствуйте! Я ваш виртуальный помощник. Чем могу помочь?' });
1041
  });
1042
  </script>
1043
  </body>
 
1181
  function sendOrderViaWhatsApp() {
1182
  if (order.cart.length === 0) { alert("Нельзя отправить пустой заказ."); return; }
1183
  const orderId = document.getElementById('orderId').textContent;
1184
+ const whatsappNumber = "{{ whatsapp_number }}".replace(/\\D/g, '');
1185
  let message = `Здравству��те! Хочу подтвердить или изменить свой заказ на Gippo312:%0A%0A*Номер заказа:* ${orderId}%0A%0A`;
1186
  order.cart.forEach(item => {
1187
  message += `*${item.name}* ${item.color !== 'N/A' ? `(${item.color})` : ''}%0A`;
 
1225
  .section { margin-bottom: 30px; padding: 20px; background-color: #fdfdff; border: 1px solid #e0e0e0; border-radius: 8px; }
1226
  form { margin-bottom: 20px; }
1227
  label { font-weight: 500; margin-top: 10px; display: block; color: #666; font-size: 0.9rem;}
1228
+ input[type="text"], input[type="number"], input[type="password"], input[type="tel"], input[type="url"], input[type="color"], textarea, select { width: 100%; padding: 10px 12px; margin-top: 5px; border: 1px solid #e0e0e0; border-radius: 6px; font-size: 0.95rem; box-sizing: border-box; transition: border-color 0.3s ease; background-color: #fff; }
1229
+ input[type="color"] { padding: 5px; height: 40px; }
1230
  input:focus, textarea:focus, select:focus { border-color: var(--bg-medium); outline: none; box-shadow: 0 0 0 2px rgba(19, 93, 102, 0.1); }
1231
  textarea { min-height: 80px; resize: vertical; }
1232
  input[type="file"] { padding: 8px; background-color: #ffffff; cursor: pointer; border: 1px solid #e0e0e0;}
 
1266
  .download-hf-button:hover { background-color: #5a6268; }
1267
  .flex-container { display: flex; flex-wrap: wrap; gap: 20px; }
1268
  .flex-item { flex: 1; min-width: 350px; }
1269
+ fieldset { border: 1px solid #e0e0e0; border-radius: 6px; padding: 15px; margin-top: 20px; }
1270
+ legend { font-weight: 600; color: var(--bg-medium); padding: 0 10px; }
1271
  .message { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem;}
1272
  .message.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb;}
1273
  .message.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb;}
 
1339
  <div class="flex-item">
1340
  <div class="section">
1341
  <h2><i class="fas fa-info-circle"></i> Информация о магазине</h2>
1342
+ <details><summary><i class="fas fa-chevron-down"></i> Развернуть/Свернуть</summary><div class="form-content"><form method="POST"><input type="hidden" name="action" value="update_org_info">
1343
+ <label for="about_us">О нас:</label><textarea id="about_us" name="about_us" rows="3">{{ organization_info.get('about_us', '') }}</textarea>
1344
+ <label for="shipping">Доставка:</label><textarea id="shipping" name="shipping" rows="3">{{ organization_info.get('shipping', '') }}</textarea>
1345
+ <label for="returns">Возврат/обмен:</label><textarea id="returns" name="returns" rows="3">{{ organization_info.get('returns', '') }}</textarea>
1346
+ <label for="contact">Контакты:</label><textarea id="contact" name="contact" rows="3">{{ organization_info.get('contact', '') }}</textarea>
1347
+ <label for="whatsapp_number">WhatsApp номер (с кодом страны):</label><input type="tel" id="whatsapp_number" name="whatsapp_number" value="{{ organization_info.get('whatsapp_number', '') }}">
1348
+ <label for="currency_code">Валюта магазина:</label>
1349
+ <select id="currency_code" name="currency_code">
1350
+ {% for code, name in currencies.items() %}
1351
+ <option value="{{ code }}" {% if organization_info.get('currency_code') == code %}selected{% endif %}>{{ name }} ({{ code }})</option>
1352
+ {% endfor %}
1353
+ </select>
1354
+ <fieldset>
1355
+ <legend><i class="fas fa-comment-dots"></i> Настройки чата</legend>
1356
+ <label for="chat_name">Имя чата:</label><input type="text" id="chat_name" name="chat_name" value="{{ organization_info.get('chat_name', '') }}">
1357
+ <label for="chat_avatar_url">URL аватарки чата:</label><input type="url" id="chat_avatar_url" name="chat_avatar_url" value="{{ organization_info.get('chat_avatar_url', '') }}">
1358
+ <label for="chat_theme_bg">Цвет фона хедера чата:</label><input type="color" id="chat_theme_bg" name="chat_theme_bg" value="{{ organization_info.get('chat_theme_bg', '#003C43') }}">
1359
+ <label for="chat_theme_accent">Акцентный цвет чата:</label><input type="color" id="chat_theme_accent" name="chat_theme_accent" value="{{ organization_info.get('chat_theme_accent', '#48D1CC') }}">
1360
+ </fieldset>
1361
+ <button type="submit" class="add-button"><i class="fas fa-save"></i> Сохранить информацию</button>
1362
+ </form></div></details>
1363
  </div>
1364
  </div>
1365
  </div>
1366
  <div class="section">
1367
  <h2><i class="fas fa-box-open"></i> Управление товарами</h2>
1368
+ <details><summary><i class="fas fa-plus-circle"></i> Добавить новый товар</summary><div class="form-content"><form id="add-product-form" method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_product"><label for="add_name">Название *:</label><input type="text" id="add_name" name="name" required><label for="add_price">Цена ({{ organization_info.get('currency_code', 'KGS') }}) *:</label><input type="number" id="add_price" name="price" step="0.01" min="0" required><label for="add_photos">Фото (до 10):</label><input type="file" id="add_photos" name="photos" accept="image/*" multiple><label for="add_description">Описание:</label><textarea id="add_description" name="description" rows="4"></textarea><button type="button" class="button ai-generate-button" onclick="generateDescription('add_photos', 'add_description', 'add_gen_lang')"><i class="fas fa-magic"></i> Сгенерировать</button><label for="add_gen_lang">Язык:</label><select id="add_gen_lang" name="gen_lang" style="width: auto; display: inline-block; margin-left: 10px;"><option>Русский</option><option>Кыргызский</option><option>Казахский</option><option>Узбекский</option></select><label for="add_category">Категория:</label><select id="add_category" name="category"><option value="Без категории">Без категории</option>{% for category in categories %}<option value="{{ category }}">{{ category }}</option>{% endfor %}</select><label>Цвета/Варианты:</label><div id="add-color-inputs"><div class="color-input-group"><input type="text" name="colors" placeholder="Розовый"><button type="button" class="remove-color-btn" onclick="removeColorInput(this)"><i class="fas fa-times"></i></button></div></div><button type="button" class="button add-color-btn" style="margin-top: 5px;" onclick="addColorInput('add-color-inputs')"><i class="fas fa-palette"></i> Добавить</button><br><div style="margin-top: 15px;"><input type="checkbox" id="add_in_stock" name="in_stock" checked><label for="add_in_stock" class="inline-label">В наличии</label></div><div style="margin-top: 5px;"><input type="checkbox" id="add_is_top" name="is_top"><label for="add_is_top" class="inline-label">Топ товар</label></div><br><button type="submit" class="add-button" style="margin-top: 20px;"><i class="fas fa-save"></i> Добавить товар</button></form></div></details>
1369
  <h3>Список товаров:</h3>
1370
+ {% if products %}<div class="item-list">{% for product in products %}<div class="item"><div style="display: flex; gap: 15px; align-items: flex-start;"><div class="photo-preview" style="flex-shrink: 0;">{% if product.get('photos') %}<a href="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ env_id }}/{{ product['photos'][0] }}" target="_blank"><img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ env_id }}/{{ product['photos'][0] }}" alt="Фото"></a>{% else %}<img src="https://via.placeholder.com/70x70.png?text=N/A" alt="Нет фото">{% endif %}</div><div style="flex-grow: 1;"><h3 style="margin-top: 0; margin-bottom: 5px; color: var(--text-dark);">{{ product['name'] }}{% if product.get('in_stock', True) %}<span class="status-indicator in-stock">В наличии</span>{% else %}<span class="status-indicator out-of-stock">Нет в наличии</span>{% endif %}{% if product.get('is_top', False) %}<span class="status-indicator top-product"><i class="fas fa-star"></i> Топ</span>{% endif %}</h3><p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p><p><strong>Цена:</strong> {{ "%.2f"|format(product.price) }} {{ organization_info.get('currency_code', 'KGS') }}</p><p class="description" title="{{ product.get('description', '') }}"><strong>Описание:</strong> {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}</p>{% set colors = product.get('colors', []) %}<p><strong>Цвета:</strong> {{ colors|select('ne', '')|join(', ') if colors|select('ne', '')|list|length > 0 else 'Нет' }}</p>{% if product.get('photos') and product['photos']|length > 1 %}<p style="font-size: 0.8rem; color: #999;">(Фото: {{ product['photos']|length }})</p>{% endif %}</div></div><div class="item-actions"><button type="button" class="button" onclick="toggleEditForm('edit-form-{{ loop.index0 }}')"><i class="fas fa-edit"></i> Редактировать</button><form method="POST" style="margin:0;" onsubmit="return confirm('Удалить товар \'{{ product['name'] }}\'?');"><input type="hidden" name="action" value="delete_product"><input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}"><button type="submit" class="delete-button"><i class="fas fa-trash-alt"></i> Удалить</button></form></div><div id="edit-form-{{ loop.index0 }}" class="edit-form-container"><h4><i class="fas fa-edit"></i> Редактирование: {{ product['name'] }}</h4><form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_product"><input type="hidden" name="product_id" value="{{ product.get('product_id', '') }}"><label>Название *:</label><input type="text" name="name" value="{{ product['name'] }}" required><label>Цена ({{ organization_info.get('currency_code', 'KGS') }}) *:</label><input type="number" name="price" step="0.01" min="0" value="{{ product['price'] }}" required><label>Заменить фото:</label><input type="file" id="edit_photos_{{ loop.index0 }}" name="photos" accept="image/*" multiple>{% if product.get('photos') %}<p style="font-size: 0.85rem; margin-top: 5px;">Текущие:</p><div class="photo-preview">{% for photo in product['photos'] %}<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ env_id }}/{{ photo }}" alt="Фото {{ loop.index }}">{% endfor %}</div>{% endif %}<label>Описание:</label><textarea id="edit_description_{{ loop.index0 }}" name="description" rows="4">{{ product.get('description', '') }}</textarea><button type="button" class="button ai-generate-button" onclick="generateDescription('edit_photos_{{ loop.index0 }}', 'edit_description_{{ loop.index0 }}', 'edit_gen_lang_{{ loop.index0 }}')"><i class="fas fa-magic"></i> Сгенерировать</button><label for="edit_gen_lang_{{ loop.index0 }}">Язык:</label><select id="edit_gen_lang_{{ loop.index0 }}" name="gen_lang" style="width: auto; display: inline-block; margin-left: 10px;"><option>Русский</option><option>Кыргызский</option><option>Казахский</option><option>Узбекский</option></select><label>Категория:</label><select name="category"><option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>{% for category in categories %}<option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>{% endfor %}</select><label>Цвета/Варианты:</label><div id="edit-color-inputs-{{ loop.index0 }}">{% set current_colors = product.get('colors', []) %}{% if current_colors and current_colors|select('ne', '')|list|length > 0 %}{% for color in current_colors %}{% if color.strip() %}<div class="color-input-group"><input type="text" name="colors" value="{{ color }}"><button type="button" class="remove-color-btn" onclick="removeColorInput(this)"><i class="fas fa-times"></i></button></div>{% endif %}{% endfor %}{% else %}<div class="color-input-group"><input type="text" name="colors" placeholder="Цвет"><button type="button" class="remove-color-btn" onclick="removeColorInput(this)"><i class="fas fa-times"></i></button></div>{% endif %}</div><button type="button" class="button add-color-btn" style="margin-top: 5px;" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')"><i class="fas fa-palette"></i> Добавить</button><br><div style="margin-top: 15px;"><input type="checkbox" id="edit_in_stock_{{ loop.index0 }}" name="in_stock" {% if product.get('in_stock', True) %}checked{% endif %}><label for="edit_in_stock_{{ loop.index0 }}" class="inline-label">В наличии</label></div><div style="margin-top: 5px;"><input type="checkbox" id="edit_is_top_{{ loop.index0 }}" name="is_top" {% if product.get('is_top', False) %}checked{% endif %}><label for="edit_is_top_{{ loop.index0 }}" class="inline-label">Топ товар</label></div><br><button type="submit" class="add-button" style="margin-top: 20px;"><i class="fas fa-save"></i> Сохранить</button></form></div></div>{% endfor %}</div>{% else %}<p>Товаров пока нет.</p>{% endif %}
1371
  </div>
1372
  </div>
1373
  <div id="chat-modal" class="modal"><div class="modal-content"><span class="button delete-button" style="float:right;" onclick="closeModal('chat-modal')">&times;</span><h2 id="chat-modal-title">Просмотр диалога</h2><div id="chat-modal-body" class="chat-message-viewer"></div></div></div>
 
1513
  def catalog(env_id):
1514
  data = load_env_data(env_id)
1515
  all_products_raw = data.get('products', [])
1516
+ organization_info = data.get('organization_info', {})
1517
+ currency_code = organization_info.get('currency_code', 'KGS')
1518
+
1519
  product_categories = set(p.get('category', 'Без категории') for p in all_products_raw)
1520
  admin_categories = set(data.get('categories', []))
1521
  all_cat_names = sorted(list(product_categories.union(admin_categories)))
 
1530
 
1531
  return render_template_string(
1532
  CATALOG_TEMPLATE, products_by_category=products_by_category, ordered_categories=ordered_categories,
1533
+ products_json=json.dumps(products_sorted_for_js), repo_id=REPO_ID, env_id=env_id, currency_code=currency_code
1534
  )
1535
 
1536
  @app.route('/<env_id>/product/<int:index>')
1537
  def product_detail(env_id, index):
1538
  data = load_env_data(env_id)
1539
+ organization_info = data.get('organization_info', {})
1540
+ currency_code = organization_info.get('currency_code', 'KGS')
1541
  all_products_raw = data.get('products', [])
1542
  products_in_stock = [p for p in all_products_raw if p.get('in_stock', True)]
1543
  products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
 
1545
  product = products_sorted[index]
1546
  except IndexError:
1547
  return "Товар не найден или отсутствует в наличии.", 404
1548
+ return render_template_string(PRODUCT_DETAIL_TEMPLATE, product=product, repo_id=REPO_ID, env_id=env_id, currency_code=currency_code)
1549
 
1550
  @app.route('/<env_id>/create_order', methods=['POST'])
1551
  def create_order(env_id):
 
1583
  @app.route('/<env_id>/order/<order_id>')
1584
  def view_order(env_id, order_id):
1585
  data = load_env_data(env_id)
1586
+ organization_info = data.get('organization_info', {})
1587
+ currency_code = organization_info.get('currency_code', 'KGS')
1588
+ whatsapp_number = organization_info.get('whatsapp_number', WHATSAPP_NUMBER)
1589
  order = data.get('orders', {}).get(order_id)
1590
+ return render_template_string(ORDER_TEMPLATE, order=order, env_id=env_id, repo_id=REPO_ID, currency_code=currency_code, whatsapp_number=whatsapp_number)
1591
 
1592
  @app.route('/<env_id>/admin', methods=['GET', 'POST'])
1593
  def admin(env_id):
 
1625
  organization_info['shipping'] = request.form.get('shipping', '').strip()
1626
  organization_info['returns'] = request.form.get('returns', '').strip()
1627
  organization_info['contact'] = request.form.get('contact', '').strip()
1628
+ organization_info['whatsapp_number'] = request.form.get('whatsapp_number', '').strip()
1629
+ organization_info['currency_code'] = request.form.get('currency_code', 'KGS')
1630
+ organization_info['chat_name'] = request.form.get('chat_name', '').strip()
1631
+ organization_info['chat_avatar_url'] = request.form.get('chat_avatar_url', '').strip()
1632
+ organization_info['chat_theme_bg'] = request.form.get('chat_theme_bg', '#003C43')
1633
+ organization_info['chat_theme_accent'] = request.form.get('chat_theme_accent', '#48D1CC')
1634
  data['organization_info'] = organization_info
1635
  save_env_data(env_id, data)
1636
  flash("Информация о магазине обновлена.", 'success')
 
1648
 
1649
  photos_list = []
1650
  photos_files = request.files.getlist('photos')
1651
+ if photos_files and any(f.filename for f in photos_files):
1652
+ if not HF_TOKEN_WRITE:
1653
+ flash("Токен HF_TOKEN_WRITE не установлен. Загрузка фото невозможна.", 'error')
1654
+ else:
1655
+ api = HfApi()
1656
+ for photo in photos_files[:10]:
1657
+ if photo and photo.filename:
1658
+ try:
1659
+ ext = os.path.splitext(photo.filename)[1].lower()
1660
+ if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: continue
1661
+ safe_name = secure_filename(name.replace(' ', '_'))[:50]
1662
+ photo_filename = f"{safe_name}_{datetime.now().strftime('%f')}{ext}"
1663
+
1664
+ photo.seek(0)
1665
+ api.upload_file(
1666
+ path_or_fileobj=photo.stream,
1667
+ path_in_repo=f"photos/{env_id}/{photo_filename}",
1668
+ repo_id=REPO_ID,
1669
+ repo_type="dataset",
1670
+ token=HF_TOKEN_WRITE
1671
+ )
1672
+ photos_list.append(photo_filename)
1673
+ except HfHubHTTPError as e:
1674
+ flash(f"Ошибка HTTP при загрузке фото {photo.filename}: {e}. Проверьте токен и права доступа к репозиторию.", 'error')
1675
+ except Exception as e:
1676
+ flash(f"Неизвестная ошибка при загрузке фото {photo.filename}: {e}", 'error')
1677
 
1678
  if action == 'add_product':
1679
  new_product = {'product_id': uuid4().hex}
 
1726
  return render_template_string(
1727
  ADMIN_TEMPLATE, products=sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower()),
1728
  categories=sorted(current_data.get('categories', [])), organization_info=current_data.get('organization_info', {}),
1729
+ chats=current_data.get('chats', {}), repo_id=REPO_ID, env_id=env_id, currencies=CURRENCIES
1730
  )
1731
 
1732
  @app.route('/generate_description_ai', methods=['POST'])
 
1762
  @app.route('/<env_id>/chat')
1763
  def chat_page(env_id):
1764
  data = load_env_data(env_id)
1765
+ organization_info = data.get('organization_info', {})
1766
+ currency_code = organization_info.get('currency_code', 'KGS')
1767
  products_in_stock = [p for p in data.get('products', []) if p.get('in_stock', True)]
1768
  products_sorted = sorted(products_in_stock, key=lambda p: (not p.get('is_top', False), p.get('name', '').lower()))
1769
+ return render_template_string(
1770
+ CHAT_TEMPLATE, products_json=json.dumps(products_sorted), repo_id=REPO_ID,
1771
+ env_id=env_id, currency_code=currency_code, organization_info=organization_info
1772
+ )
1773
 
1774
  @app.route('/<env_id>/get_chat/<chat_id>')
1775
  def get_chat_history(env_id, chat_id):