diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,4 +1,3 @@ - import os import io import base64 @@ -11,7 +10,6 @@ from uuid import uuid4 from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, Response from PIL import Image -import google.generativeai as genai import numpy as np from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError, HfHubHTTPError @@ -24,20 +22,13 @@ load_dotenv() app = Flask(__name__) app.secret_key = 'your_unique_secret_key_gippo_312_shop_54321_no_login' DATA_FILE = 'data.json' - SYNC_FILES = [DATA_FILE] - REPO_ID = "Kgshop/aiexample" HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") -GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") - STORE_ADDRESS = "Рынок Кербент, 6 ряд , 43 контейнер " WHATSAPP_NUMBER = "+996701202013" - CURRENCY_CODE = 'KGS' -CURRENCY_NAME = 'Кыргызский сом' - DOWNLOAD_RETRIES = 3 DOWNLOAD_DELAY = 5 @@ -46,12 +37,9 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %( def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY): if not HF_TOKEN_READ and not HF_TOKEN_WRITE: logging.warning("HF_TOKEN_READ/HF_TOKEN_WRITE not set. Download might fail for private repos.") - token_to_use = HF_TOKEN_READ if HF_TOKEN_READ else HF_TOKEN_WRITE - files_to_download = [specific_file] if specific_file else SYNC_FILES all_successful = True - for file_name in files_to_download: success = False for attempt in range(retries + 1): @@ -69,14 +57,14 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN success = True break except RepositoryNotFoundError: - return False + return False except HfHubHTTPError as e: if e.response.status_code == 404: if attempt == 0 and not os.path.exists(file_name): try: if file_name == DATA_FILE: with open(file_name, 'w', encoding='utf-8') as f: - json.dump({'products': [], 'categories': [], 'orders': {}, 'organization_info': {}, 'chats': {}}, f) + json.dump({'products': [], 'categories': [], 'orders': {}}, f) except Exception as create_e: pass success = False @@ -84,16 +72,13 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN else: pass except requests.exceptions.RequestException as e: - pass + pass except Exception as e: - pass - + pass if attempt < retries: time.sleep(delay) - if not success: all_successful = False - return all_successful def upload_db_to_hf(specific_file=None): @@ -102,7 +87,6 @@ def upload_db_to_hf(specific_file=None): try: api = HfApi() files_to_upload = [specific_file] if specific_file else SYNC_FILES - for file_name in files_to_upload: if os.path.exists(file_name): try: @@ -115,7 +99,7 @@ def upload_db_to_hf(specific_file=None): commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) except Exception as e: - pass + pass else: pass except Exception as e: @@ -128,65 +112,37 @@ def periodic_backup(): upload_db_to_hf() def load_data(): - default_organization_info = { - "about_us": "Мы — Gippo312, ваш надежный партнер в мире уникальных товаров. Мы предлагаем широкий ассортимент продукции, от электроники до товаров для дома, всегда стремясь к качеству и доступности. Наша миссия — сделать ваш шопинг приятным и удобным, предлагая только лучшие товары, тщательно отобранные для вас.", - "shipping": "Доставка осуществляется по всему Кыргызстану. Стоимость и сроки доставки зависят от региона и веса товара. По Бишкеку доставка возможна в течение 1-2 рабочих дней, в регионы — от 3 до 7 дней. Для уточнения деталей свяжитесь с нами.", - "returns": "Возврат и обмен товара возможен в течение 14 дней с момента покупки, при условии сохранения товарного вида, упаковки и чека. Некоторые категории товаров могут иметь особые условия возврата. Пожалуйста, свяжитесь с нами для оформления возврата или обмена.", - "contact": f"Наш магазин находится по адресу: {STORE_ADDRESS}. Связаться с нами можно по телефону: {WHATSAPP_NUMBER} или через WhatsApp по этому же номеру. Мы работаем ежедневно с 9:00 до 18:00." - } - default_data = {'products': [], 'categories': [], 'orders': {}, 'organization_info': default_organization_info, 'chats': {}} + default_data = {'products': [], 'categories': [], 'orders': {}} data = default_data try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): - raise FileNotFoundError + raise FileNotFoundError if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] if 'orders' not in data: data['orders'] = {} - if 'organization_info' not in data: data['organization_info'] = default_organization_info - if 'chats' not in data: data['chats'] = {} except FileNotFoundError: if download_db_from_hf(specific_file=DATA_FILE): try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) if not isinstance(data, dict): - data = default_data - if 'products' not in data: data['products'] = [] - if 'categories' not in data: data['categories'] = [] - if 'orders' not in data: data['orders'] = {} - if 'organization_info' not in data: data['organization_info'] = default_organization_info - if 'chats' not in data: data['chats'] = {} - except (FileNotFoundError, json.JSONDecodeError, Exception) as e: - data = default_data - else: - data = default_data - except json.JSONDecodeError: - if download_db_from_hf(specific_file=DATA_FILE): - try: - with open(DATA_FILE, 'r', encoding='utf-8') as file: - data = json.load(file) - if not isinstance(data, dict): - data = default_data + data = default_data if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] if 'orders' not in data: data['orders'] = {} - if 'organization_info' not in data: data['organization_info'] = default_organization_info - if 'chats' not in data: data['chats'] = {} except (FileNotFoundError, json.JSONDecodeError, Exception) as e: data = default_data else: data = default_data - except Exception as e: + except (json.JSONDecodeError, Exception): data = default_data - for product in data['products']: if 'product_id' not in product: product['product_id'] = uuid4().hex if any('product_id' not in p for p in data['products']): save_data(data) - if not os.path.exists(DATA_FILE): try: with open(DATA_FILE, 'w', encoding='utf-8') as f: @@ -202,2181 +158,522 @@ def save_data(data): if 'products' not in data: data['products'] = [] if 'categories' not in data: data['categories'] = [] if 'orders' not in data: data['orders'] = {} - if 'organization_info' not in data: data['organization_info'] = {} - if 'chats' not in data: data['chats'] = {} - with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf(specific_file=DATA_FILE) except Exception as e: pass -def configure_gemini(): - if not GOOGLE_API_KEY: - return False - try: - genai.configure(api_key=GOOGLE_API_KEY) - return True - except Exception as e: - return False - -def generate_ai_description_from_image(image_data, language): - if not configure_gemini(): - raise ValueError("Google AI API не настроен.") - - try: - if not image_data: - raise ValueError("Файл изображения не найден.") - image_stream = io.BytesIO(image_data) - image = Image.open(image_stream).convert('RGB') - except Exception as e: - raise ValueError(f"Не удалось обработать изображение. Убедитесь, что это действительный файл изображения.") - - base_prompt = "Напиши большой и красивый, содержательный рекламный пост минимум на 1000 символов со смайликами и 25 тематиче��ких хэштегов с ключевыми словами разных вариантов, чтобы мои клиенты могли найти меня в поиске Instagram, Google и т.д. по ключевым словам. Пост пиши исключительно под товар, который на фото, без адресов и номеров телефона." - - lang_suffix = "" - if language == "Русский": - lang_suffix = " Пиши на русском языке." - elif language == "Кыргызский": - lang_suffix = " Пиши на кыргызском языке." - elif language == "Казахский": - lang_suffix = " Пиши на казахском языке." - elif language == "Узбекский": - lang_suffix = " Пиши на узбекском языке." - - final_prompt = f"{base_prompt}{lang_suffix}" - - try: - model = genai.GenerativeModel('gemma-3-27b-it') - - response = model.generate_content([final_prompt, image]) - - if hasattr(response, 'text'): - return response.text - else: - if response.parts: - return "".join(part.text for part in response.parts if hasattr(part, 'text')) - else: - response.resolve() - return response.text - - except Exception as e: - if "API key not valid" in str(e): - raise ValueError("Внутренняя ошибка конфигурации API.") - elif " Billing account not found" in str(e): - raise ValueError("Проблема с биллингом аккаунта Google Cloud. Проверьте ваш аккаунт.") - elif "Could not find model" in str(e): - raise ValueError(f"Модель 'learnlm-2.0-flash-experimental' не найдена или недоступна.") - elif "resource has been exhausted" in str(e).lower(): - raise ValueError("Квота запросов исчерпана. Попробуйте позже.") - elif "content has been blocked" in str(e).lower(): - reason = "неизвестна" - if hasattr(e, 'response') and hasattr(e.response, 'prompt_feedback') and e.response.prompt_feedback.block_reason: - reason = e.response.prompt_feedback.block_reason - raise ValueError(f"Генерация контента заблокирована из-за политики безопасности (причина: {reason}). Попробуйте другое изображение или запрос.)") - else: - raise ValueError(f"Ошибка при генерации контента: {e}") - -def generate_chat_response(message, chat_history_from_client): - if not configure_gemini(): - return "Извините, сервис чата временно недоступен. Пожалуйста, попробуйте позже." - - data = load_data() - products = data.get('products', []) - categories = data.get('categories', []) - organization_info = data.get('organization_info', {}) - - product_info_list = [] - for p in products: - if p.get('in_stock', True): - price_display = f"{p.get('price', 0):.2f}".replace('.00', '') - product_info_list.append(f"- [ID_ТОВАРА: {p.get('product_id', 'N/A')} Название: {p.get('name', 'Без названия')}], Категория: {p.get('category', 'Без категории')}, Цена: {price_display} {CURRENCY_CODE}, Описание: {p.get('description', '')[:100]}...") - product_list_str = "\n".join(product_info_list) if product_info_list else "В данный момент нет товаров в наличии." - - category_list_str = ", ".join(categories) if categories else "Категорий пока нет." - - org_info_str = "" - if organization_info: - org_info_str += "\n\nИнформация о магазине:\n" - if organization_info.get("about_us"): - org_info_str += f"О нас: {organization_info['about_us']}\n" - if organization_info.get("shipping"): - org_info_str += f"Доставка: {organization_info['shipping']}\n" - if organization_info.get("returns"): - org_info_str += f"Возврат и обмен: {organization_info['returns']}\n" - if organization_info.get("contact"): - org_info_str += f"Контактная информация: {organization_info['contact']}\n" - - - system_instruction_content = ( - "Ты - доброжелательный и очень полезный виртуальный консультант для магазина Gippo312. " - "Твоя задача - помогать пользователям находить товары, отвечать на вопросы о них, предлагать варианты, а также предоставлять информацию о магазине. " - "Всегда будь вежлив, информативен и стремись решить проблему пользователя. " - "Никогда не выдумывай товары или категории, которых нет в предоставленных списках. " - "Когда ты предлагаешь товар, всегда указывай его название и ID, используя *точный формат*: [ID_ТОВАРА: Название: ]. Это *очень важно* для клиента. " - "Если пользователь ищет товар или категорию, предлагай несколько наиболее подходящих вариантов или перечисляй доступные из этой категории.\n\n" - f"Список доступных категорий: {category_list_str}.\n\n" - f"Список доступных товаров в магазине:\n" - f"{product_list_str}" - f"{org_info_str}\n\n" - "Если пользователь спрашивает про товары или категории, которых нет в списках, вежливо сообщи, что таких товаров/категорий нет и предложи что-то из имеющихся, или перечисли доступные категории. " - "Если вопрос касается общей информации о магазине (например, 'о нас', 'доставка', 'возврат', 'контакты'), используй данные из блока 'Информация о магазине'. " - "Старайся быть кратким, но информативным. Используй эмодзи для дружелюбности. " - "Избегай упоминания Hugging Face или Hugging Face Hub." - ) - - generated_text = "" - response = None - - try: - model = genai.GenerativeModel('gemma-3-27b-it') - - model_chat_history_for_gemini = [ - {'role': 'user', 'parts': [{'text': system_instruction_content}]} - ] - for entry in chat_history_from_client: - gemini_role = 'model' if entry['role'] == 'ai' else 'user' - model_chat_history_for_gemini.append({ - 'role': gemini_role, - 'parts': [{'text': entry['text']}] - }) - - chat = model.start_chat(history=model_chat_history_for_gemini) - - response = chat.send_message(message, generation_config={'max_output_tokens': 1000}) - - if hasattr(response, 'text'): - generated_text = response.text - elif response.parts: - generated_text = "".join(part.text for part in response.parts if hasattr(part, 'text')) - else: - response.resolve() - if hasattr(response, 'text'): - generated_text = response.text - else: - raise ValueError("AI did not return a valid text response.") - - return generated_text - - except Exception as e: - if "API key not valid" in str(e): - return "Внутренняя ошибка конфигурации API." - elif " Billing account not found" in str(e): - return "Проблема с биллингом аккаунта Google Cloud." - elif "Could not find model" in str(e): - return "Модель AI не найдена или недоступна." - elif "resource has been exhausted" in str(e).lower(): - return "Квота запросов исчерпана. Попробуйте позже." - elif "content has been blocked" in str(e).lower() or (response is not None and hasattr(response, 'prompt_feedback') and response.prompt_feedback.block_reason): - reason = response.prompt_feedback.block_reason if (response and hasattr(response, 'prompt_feedback')) else "неизвестна" - return f"Извините, Ваш запрос был заблокирован из-за политики безопасности (причина: {reason}). Пожалуйста, переформулируйте его." - else: - return f"Извините, произошла ошибка: {e}" - -CATALOG_TEMPLATE = ''' +CATEGORY_PAGE_TEMPLATE = ''' - Gippo312 - Каталог + Каталог - - +
+ Категории +
-
- - -
- {% set has_products = False %} - {% for category_name in ordered_categories %} - {% if products_by_category[category_name] %} - {% set has_products = True %} -
-
-

{{ category_name }}

- > -
- -
- {% endif %} {% endfor %} - - {% if not has_products %} -

Товары пока не добавлены.

- {% endif %} - -
-
- - + + +''' -