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