@@ -460,7 +460,8 @@ if (SENTRY_URL := env.str('SENTRY_URL', '')) and RELEASE and ENVIRONMENT: 'PromptLengthExceeded', 'InvalidParameterError', 'UnsupportedSize', - 'OutputSensitiveImageContentError' + 'OutputSensitiveImageContentError', + 'InputImageSensitiveContentError', ], ) @@ -784,7 +784,11 @@ msgstr "Загруженное изображение может содержа msgid "The generated image may contain private or prohibited content" msgstr "Готовое изображение может содержать приватный или запрещённый контент" -#: ml_model/exceptions.py:195 +#: ml_model/exceptions.py:208 +msgid "The input image may contain private or prohibited content" +msgstr "Загруженное изображение может содержать приватный или запрещённый контент" + +#: ml_model/exceptions.py:215 msgid "not specified" msgstr "не указана" @@ -11,9 +11,10 @@ from messages.services.message_service import MessageService from ml_model.exceptions import ( FileExtensionNotSupported, GenerationException, + InputImageSensitiveContentError, + OutputSensitiveImageContentError, RealPersonDetectedError, RequestBlocked, - OutputSensitiveImageContentError, ) from poller.models import Proxy from tools.chats.domain import RawSSEChunk @@ -365,6 +366,8 @@ class BytedanceModelArkAdapter: if error_code := data.get('error', {}).get('code', ''): if error_code == 'OutputImageSensitiveContentDetected': raise OutputSensitiveImageContentError + if error_code == 'InputImageSensitiveContentDetected': + raise InputImageSensitiveContentError if image_data := data.get('data'): urls = [item.get('url') for item in image_data if isinstance(item, dict) and item.get('url')] @@ -409,6 +412,8 @@ class BytedanceModelArkAdapter: return data if error_code := data.get('error', {}).get('code', None): match error_code: + case 'InputImageSensitiveContentDetected': + raise InputImageSensitiveContentError case 'InputImageSensitiveContentDetected.PrivacyInformation': raise RealPersonDetectedError case _: @@ -10,7 +10,7 @@ import requests from django.core.files import File from messages.models import Message -from ml_model.exceptions import RequestBlocked, GenerationException +from ml_model.exceptions import GenerationException, PromptLengthExceeded, RequestBlocked from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -42,6 +42,8 @@ class Grok_Imagine_Video(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: + if len(input_message.content or '') > 2000: + raise PromptLengthExceeded(max_length=2000) duration = input_message.info.get('duration', 5) if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( cost := self.TOKENS_COST * duration @@ -204,6 +204,11 @@ class OutputSensitiveImageContentError(Exception): return _('The generated image may contain private or prohibited content') +class InputImageSensitiveContentError(Exception): + def __str__(self) -> str: + return _('The input image may contain private or prohibited content') + + class ModelVersionNotAvailable(Exception): def __init__(self, version: str | None, available_versions: Iterable[str]) -> None: self.version = version @@ -121,7 +121,7 @@ class PaymentPlanUserInfoAdmin(admin.ModelAdmin): @admin.register(PaymentPlanFeature) class PaymentPlanFeatureAdmin(OrderedModelAdmin): list_display = ('plan', 'model', 'move_up_down_links') - list_filter = ('plan', 'model__category') + list_filter = ('plan__tokens_per_plan', 'model__category') class PaymentAttemptInline(admin.TabularInline): @@ -28,12 +28,13 @@ from ml_model.exceptions import ( FileUploadUnsupported, ImageAnalysisError, ImageTooLargeError, + InputImageSensitiveContentError, InvalidParameterError, ModelVersionNotAvailable, + OutputSensitiveImageContentError, PaidPlanRequiredError, PromptLengthExceeded, RequestBlocked, - OutputSensitiveImageContentError, TemplateNotFound, TemplateUnknownException, UnrecognizedFileError, @@ -196,6 +197,7 @@ class MessagesAPIView(APIView): UnrecognizedFileError, InvalidParameterError, ModelVersionNotAvailable, + InputImageSensitiveContentError, OutputSensitiveImageContentError, ImageTooLargeError, ) as exc: @@ -23,14 +23,15 @@ from ml_model.exceptions import ( ImageAnalysisError, ImageContentNotFound, ImageTooLargeError, + InputImageSensitiveContentError, InvalidParameterError, InvalidStyleCombinationError, ModelCouldNotInterpretPrompt, ModelVersionNotAvailable, + OutputSensitiveImageContentError, PromptLengthExceeded, RealPersonDetectedError, RequestBlocked, - OutputSensitiveImageContentError, ServiceHighDemandError, UnrecognizedFileError, UnsupportedSize, @@ -39,7 +40,7 @@ from ml_model.models import NeuronModel from ml_model.validators import ModelInputValidator from payments.exceptions.insufficient_balance import InsufficientBalance -from .models import Audio, Image, Video, VoiceClone, Voice, Preset +from .models import Audio, Image, Video, VoiceClone logger = logging.getLogger(__name__) @@ -219,6 +220,7 @@ class MediaAPIView(APIView): UnrecognizedFileError, FaceNotFoundError, RealPersonDetectedError, + InputImageSensitiveContentError, OutputSensitiveImageContentError, ImageTooLargeError, ) as exc: @@ -266,40 +268,6 @@ class ModelVideosAPIView(MediaAPIView): class ModelAudiosAPIVIew(MediaAPIView): manager = Audio - @extend_schema( - parameters=[ - OpenApiParameter('model', str, 'path', required=True), - ], - request=MessageSerializer, - responses={ - 201: MessageSerializer(many=True), - }, - ) - def post(self, request: Request, model: str, *args, **kwargs) -> Response: - data = get_request_data(request) - if not (request.FILES.get('file') or data.get('file')): - voice_id = data.pop('voice_id', None) - preset_id = data.pop('preset_id', None) - - try: - if voice_id: - voice = Voice.objects.get(pk=voice_id, user=request.user) - transcription = voice.transcription - elif preset_id: - voice = Preset.objects.get(uid=preset_id) - transcription = voice.metadata.get('transcription', '') - else: - return super().post(request, model, *args, **kwargs) - except (Voice.DoesNotExist, Preset.DoesNotExist): - return Response( - {'detail': _('Voice not found.')}, - status=HTTP_400_BAD_REQUEST, - ) - - info = data['info'] - data.update({'file': voice.file, 'info': {'transcription': transcription, **info}}) - return super().post(request, model, data=data, *args, **kwargs) - class ModelVoiceCloneAPIView(MediaAPIView): manager = VoiceClone @@ -14,7 +14,6 @@ from ml_model.models import NeuronModel from ml_model.selectors.ml_models_selector import NeuronModelSelector from ml_model.selectors.param_selector import ParamSelector from ml_model.serializers import ModelParameterSerializer -from tools.media.models import Preset, Voice from tools.public_api.models import APIStore from tools.public_api.selectors.api_key import APIKeySelector from tools.public_api.views.base import BaseGenerationView @@ -36,34 +35,6 @@ class AudioView(BaseGenerationView): output_content_type = ContentTypes.AUDIO description = 'Get Audio Generation from model in URL slug. Only POST Requests.' - def post(self, request, model_slug, *args, **kwargs): - if not (request.FILES.get('file') or request.data.get('file')): - api_key_value = request.headers.get('Authorization') - if (split_api_key := api_key_value.split())[0] == 'Bearer': - api_key_value = split_api_key[-1] - user = APIKeySelector.get_user_by_key(key_value=api_key_value) - voice_id = str(request.data.pop('voice_id', '')) - try: - if not voice_id: - return super().post(request, model_slug, *args, **kwargs) - elif voice_id.isdigit(): - voice = Voice.objects.get(pk=voice_id, user=user) - # transcription = voice.transcription - else: - voice = Preset.objects.get(uid=voice_id) - # transcription = voice.metadata.get('transcription', '') - except (Voice.DoesNotExist, Preset.DoesNotExist): - return Response( - {'detail': _('Voice not found.')}, - status=HTTP_400_BAD_REQUEST, - ) - request.data.update( - { - 'file': voice.file, # 'info': {'transcription': transcription, **request.data['info']} - } - ) - return super().post(request, model_slug, *args, **kwargs) - class VideoView(BaseGenerationView): output_content_type = ContentTypes.VIDEO