Spaces:
Build error
Build error
Update app.py
Browse files
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 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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": "Доставка осуществляется по всему Кыргызстану.
|
| 150 |
-
"returns": "Возврат и обмен товара возможен в течение 14
|
| 151 |
-
"contact": f"Наш магазин находится по адресу: {STORE_ADDRESS}. Связаться с нами можно по
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
}
|
| 153 |
-
default_data = {'products': [], 'categories': [], 'orders': {}, 'organization_info': default_organization_info, 'chats': {}}
|
| 154 |
|
| 155 |
-
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 =
|
| 168 |
except (FileNotFoundError, json.JSONDecodeError):
|
| 169 |
-
data =
|
| 170 |
else:
|
| 171 |
-
data =
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
|
|
|
|
| 179 |
for product in data['products']:
|
| 180 |
if 'product_id' not in product:
|
| 181 |
product['product_id'] = uuid4().hex
|
| 182 |
-
|
|
|
|
| 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} {
|
| 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 - Чат
|
| 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 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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="
|
| 784 |
-
<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('👋 Здравствуйте! Я ваш виртуальный
|
| 1021 |
-
chatHistory.push({ role: 'ai', text: 'Здравствуйте! Я ваш виртуальный
|
| 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"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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')">×</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=
|
| 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=
|
| 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=
|
| 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)
|
| 1596 |
-
|
| 1597 |
-
|
| 1598 |
-
|
| 1599 |
-
|
| 1600 |
-
|
| 1601 |
-
|
| 1602 |
-
|
| 1603 |
-
|
| 1604 |
-
|
| 1605 |
-
|
| 1606 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 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(
|
|
|
|
|
|
|
|
|
|
| 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')">×</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):
|