@@ -7,23 +7,24 @@ from ml_model.services.dalle import Dalle from ml_model.services.deepl import Deepl from ml_model.services.deepseek import Deepseek from ml_model.services.djourney import Djourney -from ml_model.services.epicphotogasm import Epicphotogasm -from ml_model.services.elevenlabs_music import Elevenlabs_Music from ml_model.services.elevenlabs import Elevenlabs +from ml_model.services.elevenlabs_music import Elevenlabs_Music +from ml_model.services.epicphotogasm import Epicphotogasm from ml_model.services.flux import Flux from ml_model.services.flux_2 import Flux_2 from ml_model.services.fluxkrea import Fluxkrea from ml_model.services.fluxlorafast import Fluxlorafast from ml_model.services.fluxproultra import Fluxproultra +from ml_model.services.fluxpulid import Fluxpulid from ml_model.services.gemini import Gemini from ml_model.services.gemini_3_1 import Gemini_3_1 -from ml_model.services.gemma import Gemma from ml_model.services.geminiimage import Geminiimage +from ml_model.services.gemma import Gemma from ml_model.services.gptimage import Gptimage from ml_model.services.granite import Granite from ml_model.services.grok import Grok -from ml_model.services.grok_image import Grok_Image from ml_model.services.grok_4_1_fast import Grok_4_1_Fast +from ml_model.services.grok_image import Grok_Image from ml_model.services.grok_imagine_video import Grok_Imagine_Video from ml_model.services.hailuo import Hailuo from ml_model.services.hunyuan import Hunyuan @@ -36,22 +37,22 @@ from ml_model.services.leonardo import Leonardo from ml_model.services.lightning import Lightning from ml_model.services.llama import Llama from ml_model.services.logoai import Logoai -from ml_model.services.lyria import Lyria from ml_model.services.ltx import Ltx +from ml_model.services.lyria import Lyria from ml_model.services.midjourney import Midjourney -from ml_model.services.minimaxvideo import Minimaxvideo from ml_model.services.minimaxmusic import Minimaxmusic from ml_model.services.minimaxmusic_lite import Minimaxmusic_Lite +from ml_model.services.minimaxvideo import Minimaxvideo from ml_model.services.mistral import Mistral from ml_model.services.musicgen import Musicgen from ml_model.services.nanobanana import Nanobanana from ml_model.services.nanobanana_2 import Nanobanana_2 from ml_model.services.perplexity import Perplexity -from ml_model.services.pulid import Pulid from ml_model.services.photon import Photon from ml_model.services.pixverse import Pixverse -from ml_model.services.prunaai import Prunaai from ml_model.services.pruna_v import Pruna_V +from ml_model.services.prunaai import Prunaai +from ml_model.services.pulid import Pulid from ml_model.services.qwen import Qwen from ml_model.services.qwen_235B import Qwen_235B from ml_model.services.qwen_3_5 import Qwen_3_5 @@ -62,9 +63,9 @@ from ml_model.services.recraft import Recraft from ml_model.services.reve import Reve from ml_model.services.runway import Runway from ml_model.services.sdxlemoji import Sdxlemoji +from ml_model.services.seedance import Seedance from ml_model.services.seedream import Seedream from ml_model.services.sora import Sora -from ml_model.services.seedance import Seedance from ml_model.services.stablediffusion import Stablediffusion from ml_model.services.stablemusic import Stablemusic from ml_model.services.suno import Suno @@ -0,0 +1,106 @@ +from datetime import timedelta +from decimal import Decimal +from io import BytesIO +import time +from typing import Any + +from django.core.files import File +from replicate.exceptions import ModelError +import requests + +from messages.models.message import Message +from ml_model.exceptions import FaceNotFoundError, GenerationException, RequestBlocked +from ml_model.models import ModelParameter +from ml_model.services.base import SimpleService +from ml_model.tasks import replicate_run +from payments.exceptions.insufficient_balance import InsufficientBalance +from payments.selectors.payment_plan_selector import PaymentPlanSelector + + +class Fluxpulid(SimpleService): + TOKEN_COST = Decimal('15.0') + ENDPOINT = 'bytedance/flux-pulid:8baa7ef2255075b46f4d91cd238c21d31181b3e6a864463f967960bb0112525b' + + parameters = [ + ModelParameter( + name='Количество изображений', + key='num_outputs', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1, 'end': 4, 'step': 1, 'default': 1}, + ), + ModelParameter( + name='Точность промпта', + key='guidance_scale', + type=ModelParameter.TypeChoices.FLOATRANGE, + values={'start': 1.0, 'end': 10.0, 'step': 0.1, 'default': 4.0}, + ), + ModelParameter( + name='Негативный промпт', + key='negative_prompt', + type=ModelParameter.TypeChoices.STR, + ), + ] + + @classmethod + def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: + price = info.get('num_outputs', 1) * cls.TOKEN_COST + + return price.quantize(Decimal('0.1'), rounding='ROUND_UP') + + def calculate_price(self, num_outputs: int) -> Decimal: + price = num_outputs * self.TOKEN_COST + + return price.quantize(Decimal('0.1'), rounding='ROUND_UP') + + def save_results(self, content: str, images: list, t: timedelta, save: bool = True) -> list[Message]: + messages: list[Message] = [] + for image in images: + messages.append( + Message( + content_object=self.store, + elapsed_time=t, + content=content, + file=File(BytesIO(requests.get(image.url).content), '.png'), + ) + ) + + if save: + return Message.objects.bulk_create(messages) + + return messages + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + num_outputs = input_message.info.get('num_outputs', 1) + if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( + cost := self.TOKEN_COST * num_outputs + ): + raise InsufficientBalance(balance, cost) + + translated_prompt = self.translate_prompt(input_message.content) + callback_data = dict( + { + 'prompt': translated_prompt, + 'main_face_image': BytesIO(input_message.file.read()), + 'output_quality': 100, + 'output_format': 'png', + 'max_sequence_length': 256, + **input_message.info, + } + ) + start_time = time.time() + try: + images = replicate_run(self.ENDPOINT, callback_data) + except ModelError as exc: + if any(error in str(exc) for error in ('E005', 'E006', 'sexual')): + raise RequestBlocked + elif exc.prediction.error == 'facexlib align face fail': + raise FaceNotFoundError + raise GenerationException from exc + + process_time = timedelta(seconds=(time.time() - start_time)) + if num_outputs == 1: + images = [images] + self.handle_invoice(input_message.content_object.model, len(images)) + msgs = self.save_results(input_message.content, images, process_time, save) + + return msgs @@ -156,3 +156,8 @@ class ServiceHighDemandError(Exception): class PaidPlanRequiredError(Exception): def __str__(self) -> str: return _('Available only in paid plan') + + +class FaceNotFoundError(Exception): + def __str__(self) -> str: + return _('Face not found in the image. Please try another image with a face.') @@ -13,6 +13,7 @@ from messages.serializers import MessageSerializer from ml_model.exceptions import ( CorruptedFileError, ExceededContextLengthError, + FaceNotFoundError, FileExtensionNotSupported, FileNotProvided, FileTooLargeError, @@ -196,6 +197,7 @@ class MediaAPIView(APIView): FileTooLargeError, ImageAnalysisError, UnrecognizedFileError, + FaceNotFoundError, ), ): return Response({'detail': f'{exc}'}, status=HTTP_400_BAD_REQUEST) @@ -1,7 +1,10 @@ # CORE SETTINGS SECRET_KEY=testtest DEBUG=true -STATIC_PATH_PREFIX=static/ +STATIC_PATH_PREFIX=static + +RELEASE=1.0.0 +ENVIRONMENT=dev # NEURON MODELS OPENAI_API_KEY=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 @@ -25,7 +28,7 @@ UPSCALE_MULTIPLIER_HOST=packet:8080 JWT_SECRET_KEY=testtest JWT_ACCESS_TOKEN_LIFETIME=604800 JWT_REFRESH_TOKEN_LIFETIME=604800 -ALLOWED_HOSTS=localhost +ALLOWED_HOSTS=localhost, 127.0.0.1 CSRF_TRUSTED_ORIGINS=http://localhost CORS_ALLOWED_ORIGINS=http://localhost:3000 TELEGRAM_BOT_TOKEN=None