From 9ddf54f39836ea1b77f2e05b8e8ff60d717bb5b7 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Fri, 23 Jan 2026 15:18:51 +0300 Subject: [PATCH] =?UTF-8?q?refactor(ai):=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D1=83=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B0=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B1=D1=83=D0=BA=D0=B5=D1=82=D0=BE=D0=B2=20-?= =?UTF-8?q?=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=81=D1=82=D0=B0=D0=BD=D1=82=D1=8B=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20-=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8E=20=D0=B2?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=D0=BD=D1=8B=D1=85=20=D0=BF=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2=20-=20=D0=9E=D0=BF=D1=82?= =?UTF-8?q?=D0=B8=D0=BC=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20AI-=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B8=D1=81=D0=B0=20-=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BD=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=20=D0=BD=D0=B0=D0=B7=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B9=20-=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D1=83=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B2=20=D0=B1=D0=B0=D0=B7=D1=83=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20-=20=D0=A3=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B6=D0=B5=D0=BB=D0=B0=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=BF=D1=80=D0=B5=D1=84=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=D0=BE=D0=B2=20-=20=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=82=D1=8C=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20gen?= =?UTF-8?q?erate=5Fand=5Fstore=20=D0=B4=D0=BB=D1=8F=20=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=B9=20=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D0=BC=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../products/services/ai/bouquet_names.py | 84 ++++++++++++++----- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/myproject/products/services/ai/bouquet_names.py b/myproject/products/services/ai/bouquet_names.py index 2857380..02e5dd5 100644 --- a/myproject/products/services/ai/bouquet_names.py +++ b/myproject/products/services/ai/bouquet_names.py @@ -19,6 +19,12 @@ class BouquetNameGenerator(BaseAIProductService): "Избегайте общих терминов. Фокусируйтесь на эмоциях, эстетике" ) + # Константы + MAX_TOKENS_GENERATION = 3000 + DEFAULT_COUNT = 500 + MAX_GENERATION_COUNT = 1000 + SKIP_PREFIXES = {'here', 'names', "i'm", 'sorry', 'i hope', 'hope'} + def generate( self, count: int = 500, @@ -38,17 +44,17 @@ class BouquetNameGenerator(BaseAIProductService): Returns: Tuple: (success, message, data) где data содержит список названий """ + # Валидация параметров + if count > self.MAX_GENERATION_COUNT: + count = self.MAX_GENERATION_COUNT + logger.warning(f"Count reduced to {self.MAX_GENERATION_COUNT}") + logger.info(f"Генерация {count} названий для букетов") # Получаем доступный AI-сервис - glm_service = self.get_glm_service() - if not glm_service: - openrouter_service = self.get_openrouter_service() - if not openrouter_service: - return False, "Нет активных AI-интеграций", None - service = openrouter_service - else: - service = glm_service + service = self.get_glm_service() or self.get_openrouter_service() + if not service: + return False, "Нет активных AI-интеграций", None # Формируем промпт prompt = f"Сгенерируй {count} креативных и привлекательных названий для букетов цветов" @@ -98,9 +104,7 @@ class BouquetNameGenerator(BaseAIProductService): for line in lines: line = line.strip() # Пропускаем пустые строки и заголовки - if not line or line.lower().startswith('here') or line.lower().startswith('names') or \ - line.lower().startswith('i\'m') or line.lower().startswith('sorry') or \ - line.lower().startswith('i hope') or line.lower().startswith('hope'): + if not line or any(line.lower().startswith(prefix) for prefix in self.SKIP_PREFIXES): continue # Удаляем номера списка @@ -119,7 +123,9 @@ class BouquetNameGenerator(BaseAIProductService): line = line.replace('**', '').replace('*', '').replace('"', '').replace("'", '').strip() if line: - names.append(line) + # Приводим к нужному формату: первое слово с заглавной, остальные строчные + normalized_line = self._normalize_case(line) + names.append(normalized_line) # Удаляем дубликаты unique_names = [] @@ -131,6 +137,28 @@ class BouquetNameGenerator(BaseAIProductService): return unique_names + def _normalize_case(self, text: str) -> str: + """ + Приводит текст к формату: первое слово с заглавной буквы, остальные строчные + Например: "романтический БУКЕТ роз" -> "Романтический букет роз" + Но сохраняет имена собственные: "Букет Van Gogh" -> "Букет Van Gogh" + """ + if not text: + return text + + # Разбиваем текст на слова + words = text.split() + + if not words: + return text + + # Первое слово с заглавной буквы, остальные как есть (сохраняем имена собственные) + first_word = words[0].capitalize() + remaining_words = words[1:] + + # Собираем обратно в строку + return ' '.join([first_word] + remaining_words) + def generate_and_store( self, count: int = 500, @@ -142,23 +170,33 @@ class BouquetNameGenerator(BaseAIProductService): Генерирует названия и сохраняет в базу данных """ from products.models import BouquetName - + success, msg, data = self.generate(count, characteristics, occasion, language) if success and data: # Сохраняем названия в базу stored_count = 0 - for name in data['names']: - BouquetName.objects.get_or_create( - name=name, - language=language, - defaults={ - 'is_approved': False - } - ) - stored_count += 1 + failed_count = 0 - return True, f"Сгенерировано и сохранено {stored_count} названий для букетов", data + for name in data['names']: + try: + BouquetName.objects.get_or_create( + name=name, + language=language, + defaults={ + 'is_approved': False + } + ) + stored_count += 1 + except Exception as e: + logger.error(f"Ошибка сохранения названия '{name}': {e}") + failed_count += 1 + + success_msg = f"Сгенерировано и сохранено {stored_count} названий для букетов" + if failed_count > 0: + success_msg += f", не удалось сохранить {failed_count} названий" + + return True, success_msg, data return success, msg, data