diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,10 +1,4 @@ -Вот полный код проекта, объединяющий все ваши требования. В него добавлен отдельный маршрут /chat с премиальным дизайном, адаптацией, анимациями и полной синхронизацией корзины. Также добавлена система сохранения диалогов и просмотр их через админ-панель. -code -Python -download -content_copy -expand_less import os import io import base64 @@ -51,9 +45,10 @@ 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.") + 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 @@ -61,7 +56,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN success = False for attempt in range(retries + 1): try: - hf_hub_download( + local_path = hf_hub_download( repo_id=REPO_ID, filename=file_name, repo_type="dataset", @@ -82,16 +77,23 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN if file_name == DATA_FILE: with open(file_name, 'w', encoding='utf-8') as f: json.dump({'products': [], 'categories': [], 'orders': {}, 'organization_info': {}, 'chat_sessions': {}}, f) - except Exception: + except Exception as create_e: pass success = False break - except Exception: + else: + pass + except requests.exceptions.RequestException as e: + pass + except Exception as e: pass + if attempt < retries: time.sleep(delay) + if not success: all_successful = False + return all_successful def upload_db_to_hf(specific_file=None): @@ -100,6 +102,7 @@ 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: @@ -111,146 +114,280 @@ def upload_db_to_hf(specific_file=None): token=HF_TOKEN_WRITE, commit_message=f"Sync {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) - except Exception: + except Exception as e: pass - except Exception: + else: + pass + except Exception as e: pass def periodic_backup(): + backup_interval = 1800 while True: - time.sleep(1800) + time.sleep(backup_interval) upload_db_to_hf() def load_data(): default_organization_info = { - "about_us": "Мы — Gippo312...", - "shipping": "Доставка по всему Кыргызстану...", - "returns": "Возврат в течение 14 дней...", - "contact": f"Адрес: {STORE_ADDRESS}. Тел: {WHATSAPP_NUMBER}" + "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, 'chat_sessions': {}} - + data = default_data try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) - except (FileNotFoundError, json.JSONDecodeError): + if not isinstance(data, dict): + 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 'chat_sessions' not in data: data['chat_sessions'] = {} + 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) - except Exception: + 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 'chat_sessions' not in data: data['chat_sessions'] = {} + except (FileNotFoundError, json.JSONDecodeError, Exception) as e: data = default_data else: data = default_data - - 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 'chat_sessions' not in data: data['chat_sessions'] = {} - if 'organization_info' not in data: data['organization_info'] = default_organization_info + 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 + 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 'chat_sessions' not in data: data['chat_sessions'] = {} + except (FileNotFoundError, json.JSONDecodeError, Exception) as e: + data = default_data + else: + data = default_data + except Exception as e: + data = default_data for product in data['products']: if 'product_id' not in product: product['product_id'] = uuid4().hex - - if not os.path.exists(DATA_FILE): + 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: + json.dump(default_data, f) + except Exception as create_e: + pass return data def save_data(data): try: - if not isinstance(data, dict): return + if not isinstance(data, dict): + return + 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 'chat_sessions' not in data: data['chat_sessions'] = {} + 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: + except Exception as e: pass def configure_gemini(): - if not GOOGLE_API_KEY: return False + if not GOOGLE_API_KEY: + return False try: genai.configure(api_key=GOOGLE_API_KEY) return True - except Exception: + except Exception as e: return False def generate_ai_description_from_image(image_data, language): - if not configure_gemini(): raise ValueError("Google AI API не настроен.") + 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: - image = Image.open(io.BytesIO(image_data)).convert('RGB') - lang_prompt = f"Пиши на языке: {language}." - prompt = f"Напиши рекламный пост для Instagram про этот товар. {lang_prompt}" - model = genai.GenerativeModel('gemma-3-27b-it') - response = model.generate_content([prompt, image]) - return response.text if hasattr(response, 'text') else "Ошибка генерации" + model = genai.GenerativeModel('gemini-1.5-flash') + + 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: - raise ValueError(f"Ошибка: {str(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"Модель 'gemini-1.5-flash' не найдена или недоступна.") + 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, session_id=None): +def generate_chat_response(message, chat_history_from_client, session_id): if not configure_gemini(): - return "Сервис временно недоступен." + return "Извините, сервис чата временно недоступен. Пожалуйста, попробуйте позже." data = load_data() products = data.get('products', []) - org_info = data.get('organization_info', {}) - - # Сохранение сообщения пользователя - if session_id: - if session_id not in data['chat_sessions']: - data['chat_sessions'][session_id] = {'timestamp': datetime.now().isoformat(), 'messages': []} - data['chat_sessions'][session_id]['messages'].append({'role': 'user', 'text': message, 'time': datetime.now().isoformat()}) - save_data(data) + categories = data.get('categories', []) + organization_info = data.get('organization_info', {}) - product_list = "\n".join([f"- [ID_ТОВАРА: {p.get('product_id')} Название: {p.get('name')}] Цена: {p.get('price')} {CURRENCY_CODE}" for p in products if p.get('in_stock', True)]) - if not product_list: product_list = "Нет товаров." - - system_instruction = ( - "Ты - консультант магазина Gippo312. " - "Твоя цель - продавать товары. Будь вежлив и краток. " - "ОБЯЗАТЕЛЬНОЕ ПРАВИЛО: Если ты рекомендуешь товар, используй СТРОГИЙ формат: " - "[ID_ТОВАРА: Название: ]. " - f"Список товаров:\n{product_list}\n" - f"О нас: {org_info.get('about_us')}\n" - f"Контакты: {org_info.get('contact')}" + 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') - history_gemini = [{'role': 'user', 'parts': [{'text': system_instruction}]}] - - # Ограничиваем историю для контекста, чтобы не перегружать модель, но сохраняем всё в БД - recent_history = chat_history_from_client[-10:] - for entry in recent_history: - role = 'model' if entry['role'] == 'ai' else 'user' - history_gemini.append({'role': role, 'parts': [{'text': entry['text']}]}) - - chat = model.start_chat(history=history_gemini) - response = chat.send_message(message) - text_response = response.text - - # Сохранение ответа ИИ - if session_id: - data = load_data() # Перезагружаем на случай изменений - if session_id in data['chat_sessions']: - data['chat_sessions'][session_id]['messages'].append({'role': 'ai', 'text': text_response, 'time': datetime.now().isoformat()}) - data['chat_sessions'][session_id]['last_update'] = datetime.now().isoformat() - save_data(data) + model = genai.GenerativeModel('gemini-1.5-flash') + + 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.") + + if 'chat_sessions' not in data: + data['chat_sessions'] = {} + if session_id not in data['chat_sessions']: + data['chat_sessions'][session_id] = {'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'history': []} - return text_response - except Exception as e: - return f"Ошибка ИИ: {str(e)}" + data['chat_sessions'][session_id]['history'].append({'role': 'user', 'text': message, 'timestamp': datetime.now().isoformat()}) + data['chat_sessions'][session_id]['history'].append({'role': 'ai', 'text': generated_text, 'timestamp': datetime.now().isoformat()}) + save_data(data) -# --- TEMPLATES --- + return generated_text -CHAT_STANDALONE_TEMPLATE = ''' + 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}" +CHAT_TEMPLATE = ''' - AI Чат - Gippo312 + Gippo312 - Чат с EVA - -
-
- - Bot -
-

EVA Assistant

-
Online
-
+
+
+ Gippo312 Logo +

Чат с EVA

+
-
-
- - -
-
-
- -
-
- Привет! Я EVA, ваш персональный помощник в Gippo312. 🛍️
Я помогу найти лучшие товары. Что вас интересует? - Сейчас -
-
- -
-
-
- -
-
- -
- -
- - -