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