@@ -680,6 +680,14 @@ msgstr "" msgid "The file may be corrupted. Please try another one." msgstr "Возможно, файл повреждён. Попробуйте загрузить другой файл." +#: ml_model/exceptions.py:65 +msgid "File Uploading Not supported" +msgstr "Загрузка файлов не поддерживается" + +#: ml_model/exceptions.py:65 +msgid "Unable to recognize the file" +msgstr "Не удаётся распознать файл" + #: ml_model/exceptions.py:73 msgid "The length of the context has been exceeded." msgstr "Длина контекста превышена." @@ -1519,6 +1527,9 @@ msgstr "Режим «Плавное движение» доступен толь msgid "This video duration is not allowed for %(quality)s quality." msgstr "Для качества %(quality)s такая продолжительность видео не поддерживается." +msgid "The model could not analyze your request. Please rephrase it and try again" +msgstr "Модель не смогла проанализировать ваш запрос. Перефразируйте его и попробуйте снова" + #~ msgid "Account is already confirmed" #~ msgstr "Аккаунт уже подтвержден" @@ -1,3 +1,4 @@ +import re import time from datetime import timedelta from uuid import uuid4 @@ -68,6 +69,11 @@ class Message(models.Model): def __str__(self) -> str: return f'Сообщение {self.pk}' + def save(self, *args, **kwargs): + if self.content: + self.content = re.sub(r'\x00', '', self.content) + super().save(*args, **kwargs) + class Meta: verbose_name = 'Сообщение' verbose_name_plural = 'Сообщения' @@ -7,6 +7,8 @@ import openpyxl from io import BytesIO +from ml_model.exceptions import UnrecognizedFileError + class FileProcessingService: @classmethod @@ -18,7 +20,7 @@ class FileProcessingService: for format_name, required_file in signatures.items(): if required_file in namelist: return format_name - raise + raise UnrecognizedFileError return raw_file_extension @classmethod @@ -30,7 +30,11 @@ from PIL import Image from backend import settings from messages.models import BaseStore, Message from ml_model.constants import TEMPORARY_TEST_TEXT -from ml_model.exceptions import FileExtensionNotSupported, CorruptedFileError +from ml_model.exceptions import ( + CorruptedFileError, + FileExtensionNotSupported, + FileUploadUnsupported, +) from ml_model.models import ModelConfiguration, NeuronModel from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService @@ -116,6 +120,8 @@ class Chatgpt(SimpleService): chunks = [] text_chunks: list[str] = [] if file: + if model_name == 'gpt-oss-120b': + raise FileUploadUnsupported file_service = FileProcessingService file_bytes = input_message.file.read() kind = filetype.guess(file_bytes[:20]) @@ -37,16 +37,14 @@ class Grok_4_1_Fast(SimpleService): return price.quantize(Decimal('0.01'), rounding='ROUND_UP') def save_results(self, content: str, t: timedelta, save: bool = True) -> list[Message]: - msgs = [ - Message( - content=content, - content_object=self.store, - elapsed_time=t, - ) - ] + msg = Message( + content=content, + content_object=self.store, + elapsed_time=t, + ) if save: - return Message.objects.bulk_create(msgs) - return msgs + msg.save() + return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: callback_data = { @@ -62,9 +62,12 @@ class Hunyuan(SimpleService): def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: quality = info['quality'] duration = info['duration'] - motion_mode = info['motion_mode'] + motion_mode_ru = info['motion_mode'] sound_effect_switch = info['sound_effect_switch'] - price = cls.UNIT_PRICE * cls.PRICING_UNITS[quality][duration][motion_mode] + try: + price = cls.UNIT_PRICE * cls.PRICING_UNITS[quality][duration][cls.MOTION_MODES[motion_mode_ru]] + except KeyError: + return None if sound_effect_switch: price += 10 * cls.UNIT_PRICE return price.quantize(Decimal('0.1'), rounding='ROUND_UP') @@ -97,8 +100,7 @@ class Hunyuan(SimpleService): modes_for_duration = self.PRICING_UNITS[quality].get(duration) if not modes_for_duration: raise InvalidParameterError( - _('This video duration is not allowed for %(quality)s quality.') - % {'quality': quality} + _('This video duration is not allowed for %(quality)s quality.') % {'quality': quality} ) units = modes_for_duration.get(motion_mode, {}) if not units: @@ -11,7 +11,12 @@ from django.core.files import File from replicate.exceptions import ModelError from messages.models import Message -from ml_model.exceptions import ImageContentNotFound, GenerationException, RequestBlocked +from ml_model.exceptions import ( + ImageContentNotFound, + GenerationException, + ModelCouldNotInterpretPrompt, + RequestBlocked, +) from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -62,7 +67,7 @@ class Nanobanana(SimpleService): resolution = input_message.info.get('resolution', '2K') if version == 'nano-banana-pro' else None callback_data = dict( { - 'prompt': self.translate_prompt(input_message.content), + 'prompt': input_message.content, **input_message.info, } ) @@ -78,6 +83,8 @@ class Nanobanana(SimpleService): except ModelError as exc: if exc.prediction.error == 'No image content found in response': raise ImageContentNotFound + elif exc.prediction.error in ('400', 'Failed to generate image.'): + raise ModelCouldNotInterpretPrompt from exc elif any(error in str(exc) for error in ('E005', 'E006', 'sexual')): raise RequestBlocked raise GenerationException from exc @@ -28,12 +28,12 @@ class Pixverse(SimpleService): @classmethod def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: duration = info['duration'] - resolution = info['resolution'] - price = cls.TOKENS_COST[resolution] * duration + quality = info['quality'] + price = cls.TOKENS_COST[quality] * duration return price.quantize(Decimal('0.1'), rounding='ROUND_UP') - def calculate_price(self, resolution: str, duration: int) -> Decimal: - return (self.TOKENS_COST[resolution] * duration).quantize(Decimal('0.1'), rounding='ROUND_UP') + def calculate_price(self, quality: str, duration: int) -> Decimal: + return (self.TOKENS_COST[quality] * duration).quantize(Decimal('0.1'), rounding='ROUND_UP') def save_results(self, content: str, t: timedelta, video: str, save: bool = True) -> list[Message]: msg = Message( @@ -48,15 +48,15 @@ class Pixverse(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: duration = input_message.info.get('duration', 5) - resolution = input_message.info.pop('resolution', '1080p') + quality = input_message.info.pop('quality', '1080p') if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( - cost := self.TOKENS_COST[resolution] * duration + cost := self.TOKENS_COST[quality] * duration ): raise InsufficientBalance(balance, cost) thinking_types = {'авто': 'auto', 'выкл.': 'disabled', 'вкл.': 'enabled'} callback_data = { 'prompt': self.translate_prompt(input_message.content), - 'quality': resolution, + 'quality': quality, 'thinking_type': thinking_types[input_message.info.pop('thinking_type', 'авто').lower()], **input_message.info, } @@ -70,6 +70,6 @@ class Pixverse(SimpleService): raise RequestBlocked raise GenerationException from exc process_time = timedelta(seconds=(time.time() - start_time)) - self.handle_invoice(input_message.content_object.model, resolution=resolution, duration=duration) + self.handle_invoice(input_message.content_object.model, quality=quality, duration=duration) msgs = self.save_results(input_message.content, process_time, video, save) return msgs @@ -8,9 +8,10 @@ from typing import Any import filetype import requests from django.core.files import File +from replicate.exceptions import ModelError from messages.models import Message -from ml_model.exceptions import FileExtensionNotSupported, CorruptedFileError +from ml_model.exceptions import FileExtensionNotSupported, CorruptedFileError, GenerationException from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -63,7 +64,12 @@ class Wan_Lite(SimpleService): input_message.file.close() callback_data.update({'image': image}) start_time = time.time() - video = replicate_run('wan-video/wan-2.2-5b-fast', callback_data) + try: + video = replicate_run('wan-video/wan-2.2-5b-fast', callback_data) + except ModelError as exc: + if 'image file' in exc.prediction.error: + raise CorruptedFileError from exc + raise GenerationException from exc process_time = timedelta(seconds=(time.time() - start_time)) self.handle_invoice(input_message.content_object.model, resolution=resolution) msgs = self.save_results(input_message.content, process_time, video, save) @@ -60,6 +60,16 @@ class CorruptedFileError(Exception): return _('The file may be corrupted. Please try another one.') +class FileUploadUnsupported(Exception): + def __str__(self) -> str: + return _('File Uploading Not supported') + + +class UnrecognizedFileError(Exception): + def __str__(self) -> str: + return _('Unable to recognize the file') + + class FileTooLargeError(Exception): def __init__(self, max_mb_size: int) -> None: self.max_mb_size = max_mb_size @@ -101,6 +111,11 @@ class ImageContentNotFound(Exception): return _('No image content found in response. Try a different request') +class ModelCouldNotInterpretPrompt(Exception): + def __str__(self): + return _('The model could not analyze your request. Please rephrase it and try again') + + class ImageAnalysisError(Exception): def __str__(self): return _('Image analysis error. Please try another image.') @@ -25,7 +25,9 @@ from ml_model.exceptions import ( ExceededContextLengthError, FileExtensionNotSupported, FileTooLargeError, + FileUploadUnsupported, ImageAnalysisError, + UnrecognizedFileError, PaidPlanRequiredError, PromptLengthExceeded, RequestBlocked, @@ -178,6 +180,8 @@ class MessagesAPIView(APIView): CorruptedFileError, FileTooLargeError, ImageAnalysisError, + FileUploadUnsupported, + UnrecognizedFileError, ) as exc: return Response({'detail': f'{exc}'}, status=HTTP_400_BAD_REQUEST) except TemplateNotFound as exc: @@ -16,9 +16,11 @@ from ml_model.exceptions import ( FileExtensionNotSupported, FileNotProvided, FileTooLargeError, + UnrecognizedFileError, ImageAnalysisError, ImageContentNotFound, InvalidParameterError, + ModelCouldNotInterpretPrompt, InvalidStyleCombinationError, PromptLengthExceeded, RequestBlocked, @@ -183,6 +185,7 @@ class MediaAPIView(APIView): RequestBlocked, FileNotProvided, ImageContentNotFound, + ModelCouldNotInterpretPrompt, InvalidStyleCombinationError, InvalidParameterError, PromptLengthExceeded, @@ -192,6 +195,7 @@ class MediaAPIView(APIView): CorruptedFileError, FileTooLargeError, ImageAnalysisError, + UnrecognizedFileError, ), ): return Response({'detail': f'{exc}'}, status=HTTP_400_BAD_REQUEST)