@@ -54,6 +54,13 @@ from payments.models.payment_plan import PaymentPlan
from payments.selectors.payment_plan_selector import PaymentPlanSelector
from payments.services.payment_plan_service import PaymentPlanService
+from django.conf import settings
+from rest_framework.exceptions import APIException
+import logging
+import httpx
+
+
+logger = logging.getLogger(__name__)
class BusinessHostService:
def __init__(self, user: CustomUserModel):
@@ -118,8 +125,23 @@ class BusinessHostService:
serializer = NewBusinessAccountSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
- account = self.create_existing(**serializer.validated_data).account
- return BusinessAccountDataSerializer(account, context={'account_type': 'business_account'})
+ email = serializer.validated_data['email']
+ if CustomUserModel.objects.filter(email=serializer.validated_data.get("email")).exists():
+ raise ValueError("Этот email уже зарегистрирован в системе.")
+ try:
+ response = httpx.get(settings.EMAIL_CHECKER_URL, params={'api_key': settings.EMAIL_CHECKER_API_KEY, 'email': email})
+ if response.status_code == 422:
+ logger.error("Ошибка сервиса EMAILCHECKER.")
+ raise APIException(400, "не получилось отправить запрос, попробуйте позже")
+ elif response.status_code < 400 and response.json() and response.json().get('deliverability') == "DELIVERABLE":
+ account = self.create_existing(**serializer.validated_data).account
+ return BusinessAccountDataSerializer(account, context={'account_type': 'business_account'})
+ else:
+ logger.error("Undeliverability email.")
+ raise Exception("Email недействительный")
+ except Exception as exc:
+ logger.exception("Ошибка при создании учетной записи:")
+ raise APIException(f"Произошла ошибка при создании учетной записи: {exc}") from exc
def update_token_limit(
self,
@@ -16,6 +16,10 @@ from authentication.services.email_token_service import EmailTokenService
from core.minio_service import MinIOService
from reports.models.error_report import ErrorReport
+from django.core.mail import send_mail
+from django.utils.html import strip_tags
+
+
logger = logging.getLogger(__name__)
@@ -1,6 +1,7 @@
from typing import Any, Tuple
from uuid import UUID
+from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.db.models import Q, QuerySet
from django.db.transaction import atomic
@@ -36,6 +37,7 @@ from authentication.serializers import (
)
from authentication.services.email_service import EmailService
from authentication.services.utm_service import UTMService
+from authentication.tasks import send_corporate_offer_task
from authentication.utils import get_client_ip
from core.minio_service import MinIOService
from payments.services.referral_account import ReferralAccountService
@@ -68,7 +70,6 @@ class UserService:
)
user.save()
-
EmailService(user).send_reg_conf_email()
if referer_username := user_data.get('referer'):
try:
@@ -80,6 +81,10 @@ class UserService:
ReferralAccountService.create_invite(referer_account=referer.referral_account, invitee=user)
except CustomUserModel.DoesNotExist:
pass
+
+ email_domain = user.email.split('@')[-1]
+ if email_domain not in settings.VALID_EMAIL_DOMAINS:
+ send_corporate_offer_task.apply_async(args=[user.email], countdown=5)
return user
def create_user_telegram(self, request: Request) -> TelegramUser:
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Корпоративный аккаунт AIR: работа с платформой для всей команды
+
+
+Здравствуйте!
+В AIR можно оформить корпоративный аккаунт прямо на платформе, чтобы упростить работу для ваших коллег:
+
+ - Приглашайте неограниченное количество сотрудников
+ - Используйте общий баланс
+ - Гибко управляйте доступами и ролями
+
+Оформить корпоративный аккаунт можно в настройках профиля. Если возникнут вопросы — мы всегда на связи!
+Нужна помощь с оформлением доступа к платформе по ЭДО? Обращайтесь на info@air.fail
+Всегда с вами,
+Команда AIR
+
+
@@ -0,0 +1,22 @@
+from celery import shared_task
+
+from django.template.loader import get_template
+
+from authentication.services.email_service import EmailService
+
+import logging
+
+
+logger = logging.getLogger(__name__)
+
+@shared_task
+def send_corporate_offer_task(email: str):
+ try:
+ template = get_template('corporate_offer_letter.html')
+ except Exception as exc:
+ logger.warning(f"Template error: {exc}, email: {email}")
+ return
+ html_message = template.render()
+ EmailService(None).send_email("Корпоративный аккаунт AIR", html_message, email)
+
+
@@ -169,6 +169,7 @@ TEMPLATES = [
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'core' / 'templates',
+ BASE_DIR / 'authentication' / 'templates',
],
'APP_DIRS': True,
'OPTIONS': {
@@ -327,6 +328,9 @@ FLUX_API_KEY = env.str('FLUX_API_KEY', 'defaultapikey')
OPENROUTER_API_KEY = env.str('OPENROUTER_API_KEY', 'defaultapikey')
FAL_API_KEY = env.str('FAL_API_KEY', 'defaultapikey')
+EMAIL_CHECKER_API_KEY = env.str('EMAIL_CHECKER_API_KEY', 'defaultapikey')
+EMAIL_CHECKER_URL = env.str('EMAIL_CHECKER_URL', 'https://emailvalidation.abstractapi.com/v1/')
+
OPENAI_PROXY_HOST = env.str('OPENAI_PROXY_HOST', 'neuron-proxy:8080')
UPSCALE_MULTIPLIER_HOST = env.str('UPSCALE_MULTIPLIER_HOST', 'packet:8080')
@@ -344,6 +348,7 @@ EMAIL_PORT = env.int('EMAIL_PORT', default=143)
EMAIL_HOST_USER = env.str('EMAIL_HOST_USER', default='defaultuser')
EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD', default='defaultpass')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
+VALID_EMAIL_DOMAINS = env.str('VALID_EMAIL_DOMAINS', default='defaultdomain')
USER_CONFIRMATION_URL = env.str('USER_CONFIRMATION_URL', default='http://localhost:3000')
USER_PASSWORD_RESET_URL = env.str('USER_PASSWORD_RESET_URL', default='http://localhost:3000')
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-05-10 12:12+0300\n"
+"POT-Creation-Date: 2025-05-27 10:31+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -20,7 +20,7 @@ msgstr ""
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
"(n%100>=11 && n%100<=14)? 2 : 3);\n"
-#: achievements/admin.py:11 achievements/models.py:19 ml_model/models.py:32
+#: achievements/admin.py:11 achievements/models.py:19 ml_model/models.py:34
#: stories/models.py:18
msgid "Icon"
msgstr "Миниатюра"
@@ -33,17 +33,17 @@ msgstr "Достижение"
msgid "Achievements"
msgstr "Достижения"
-#: achievements/models.py:14 ml_model/models.py:25 ml_model/models.py:52
-#: ml_model/models.py:231 ml_model/models.py:357
+#: achievements/models.py:14 ml_model/models.py:27 ml_model/models.py:47
+#: ml_model/models.py:79 ml_model/models.py:289 ml_model/models.py:434
msgid "Slug"
msgstr "Ярлык"
-#: achievements/models.py:16 ml_model/models.py:51 ml_model/models.py:146
-#: ml_model/models.py:230 ml_model/models.py:355 payments/models/payment.py:52
+#: achievements/models.py:16 ml_model/models.py:78 ml_model/models.py:186
+#: ml_model/models.py:288 ml_model/models.py:432 payments/models/payment.py:52
msgid "Description"
msgstr "Описание"
-#: achievements/models.py:43 authentication/models/business_host.py:21
+#: achievements/models.py:43 authentication/models/business_host.py:22
#: authentication/models/email_token.py:12 authentication/models/user.py:247
#: authentication/models/user.py:248 authentication/models/user_telegram.py:22
#: authentication/models/user_vk.py:12 payments/models/invoice.py:15
@@ -69,8 +69,8 @@ msgid "Business account not found"
msgstr "Сотрудник не найден"
#: authentication/exceptions/business_host_exceptions/access_denied.py:6
-#: authentication/services/business_account_service.py:103
-#: authentication/services/business_account_service.py:106
+#: authentication/services/business_account_service.py:105
+#: authentication/services/business_account_service.py:108
msgid "You do not have sufficient rights to perform this action"
msgstr "У вас недостаточно прав для выполнения этого действия"
@@ -149,9 +149,11 @@ msgid "Group"
msgstr "Группа"
#: authentication/models/business_account.py:61
-msgid "Impossible to add this employee to this group which does not belong to this "
+msgid ""
+"Impossible to add this employee to this group which does not belong to this "
"company"
-msgstr "Невозможно добавить сотрудника к группе, когда он не принадлежит данной "
+msgstr ""
+"Невозможно добавить сотрудника к группе, когда он не принадлежит данной "
"компании"
#: authentication/models/business_account.py:70
@@ -162,8 +164,8 @@ msgstr "Дочерний Бизнес Аккаунт"
msgid "Child Business Accounts"
msgstr "Дочерние Бизнес Аккаунты"
-#: authentication/models/business_group.py:8 ml_model/models.py:24
-#: ml_model/models.py:145 ml_model/models.py:348
+#: authentication/models/business_group.py:8 ml_model/models.py:26
+#: ml_model/models.py:185 ml_model/models.py:425
#: payments/models/payment_plan.py:27 stories/models.py:12 stories/models.py:35
#: tools/chats/models.py:9
msgid "Title"
@@ -177,81 +179,85 @@ msgstr "Бизнес Группа"
msgid "Business Groups"
msgstr "Бизнес Группы"
-#: authentication/models/business_host.py:30
+#: authentication/models/business_host.py:31
msgid "Affiliated by"
msgstr "Кем привлечена"
-#: authentication/models/business_host.py:35 authentication/models/user.py:153
+#: authentication/models/business_host.py:36 authentication/models/user.py:153
#: authentication/models/whitelist.py:16 payments/models/promocode.py:85
msgid "Is active"
msgstr "Является активной"
-#: authentication/models/business_host.py:42
+#: authentication/models/business_host.py:43
msgid "Sector"
msgstr "Сектор"
-#: authentication/models/business_host.py:44
+#: authentication/models/business_host.py:45
msgid "Planned amount of workers"
msgstr "Планируемое число сотрудников"
-#: authentication/models/business_host.py:49
+#: authentication/models/business_host.py:50
msgid "Usage intensity"
msgstr "Частота использования"
-#: authentication/models/business_host.py:56
+#: authentication/models/business_host.py:57
msgid "Token low balance cap"
msgstr "Предел низкого баланса"
-#: authentication/models/business_host.py:61
+#: authentication/models/business_host.py:62
msgid "Emails token low balance cap"
msgstr "Email'ы для рассылки по низкому балансу"
-#: authentication/models/business_host.py:63
+#: authentication/models/business_host.py:64
msgid "Token low balance cap enabled"
msgstr "Рассылка по низкому балансу включена"
-#: authentication/models/business_host.py:66
+#: authentication/models/business_host.py:67
msgid "ITN"
msgstr "ИНН"
-#: authentication/models/business_host.py:67
+#: authentication/models/business_host.py:68
msgid "PSRN"
msgstr "ОГРН"
-#: authentication/models/business_host.py:68 ml_model/models.py:50
-#: ml_model/models.py:228 tools/public_api/models.py:30
+#: authentication/models/business_host.py:69 ml_model/models.py:46
+#: ml_model/models.py:77 ml_model/models.py:286 tools/public_api/models.py:30
msgid "Name"
msgstr "Наименование"
-#: authentication/models/business_host.py:71
+#: authentication/models/business_host.py:72
msgid "Preffered name"
msgstr "Предпочтительное имя"
-#: authentication/models/business_host.py:72
+#: authentication/models/business_host.py:73
msgid "Corporate email"
msgstr "Корпоративная почта"
-#: authentication/models/business_host.py:74
+#: authentication/models/business_host.py:75
msgid "Corporate phone"
msgstr "Корпоративный телефон"
-#: authentication/models/business_host.py:76
+#: authentication/models/business_host.py:77
msgid "Job title"
msgstr "Наименование работ"
-#: authentication/models/business_host.py:81
+#: authentication/models/business_host.py:83
msgid "Allowed models"
msgstr "Разрешенные модели"
-#: authentication/models/business_host.py:84
+#: authentication/models/business_host.py:87
+msgid "Private models"
+msgstr "Приватные модели"
+
+#: authentication/models/business_host.py:90
msgid "Log history enabled"
msgstr "История логов включена"
-#: authentication/models/business_host.py:103
+#: authentication/models/business_host.py:109
msgid "Business Account"
msgstr "Бизнес Аккаунт"
-#: authentication/models/business_host.py:104
+#: authentication/models/business_host.py:110
msgid "Business Accounts"
msgstr "Бизнес Аккаунты"
@@ -267,10 +273,6 @@ msgstr "IT"
msgid "Finance"
msgstr "Финансы"
-#: authentication/models/business_host.py:86
-msgid "Private models"
-msgstr "Приватные модели"
-
#: authentication/models/choices.py:9
msgid "Tourism"
msgstr "Туризм"
@@ -323,7 +325,7 @@ msgstr "Админ"
msgid "Security"
msgstr "Безопасность"
-#: authentication/models/email_token.py:15 ml_model/models.py:147
+#: authentication/models/email_token.py:15 ml_model/models.py:187
msgid "Key"
msgstr "Ключ"
@@ -400,7 +402,7 @@ msgid "Phonenumber"
msgstr "Номер телефона"
#: authentication/models/user_telegram.py:27
-#: authentication/models/user_vk.py:14 ml_model/models.py:318
+#: authentication/models/user_vk.py:14 ml_model/models.py:392
#: payments/models/invoice.py:11 stories/models.py:15 tools/chats/models.py:10
msgid "Created at"
msgstr "Когда создан"
@@ -458,12 +460,13 @@ msgstr "Вайтлист для отмены политик"
msgid "Whitelists to cancel policies"
msgstr "Вайтлисты для отмены политик"
-#: authentication/selectors/business_host_selector.py:40
-#: authentication/selectors/business_host_selector.py:83
+#: authentication/selectors/business_host_selector.py:38
+#: authentication/selectors/business_host_selector.py:81
msgid "User haven't rights to access host account information"
-msgstr "У пользователя недостаточно прав для просмотра информации бизнес-аккаунта"
+msgstr ""
+"У пользователя недостаточно прав для просмотра информации бизнес-аккаунта"
-#: authentication/selectors/business_host_selector.py:58
+#: authentication/selectors/business_host_selector.py:56
msgid "Host user is not registered for this account"
msgstr "Пользователь бизнес-аккаунта не зарегистрирован для этого аккаунта"
@@ -479,53 +482,57 @@ msgstr "Бизнес-аккаунт для данного юзера не най
msgid "Invited account can either accept or reject an invitation"
msgstr "Приглашенный аккаунт может принять или отклонить приглашение"
-#: authentication/services/business_account_service.py:81
+#: authentication/services/business_account_service.py:83
msgid "Account is already confirmed"
msgstr "Аккаунт уже подтвержден"
-#: authentication/services/business_account_service.py:100
+#: authentication/services/business_account_service.py:102
#, fuzzy
#| msgid "Passwords do not match"
msgid "Passwords don't match"
msgstr "Пароли не совпадают"
-#: authentication/services/business_host_service.py:151
+#: authentication/services/business_account_service.py:111
+msgid "You cannot change the password of an unconfirmed e-mail user."
+msgstr "Вы не можете изменить пароль неподтвержденного по e-mail пользователя."
+
+#: authentication/services/business_host_service.py:171
msgid "No user_email is provided"
msgstr ""
-#: authentication/services/email_service.py:47
+#: authentication/services/email_service.py:51
msgid "Error occured when proceed email sending"
msgstr "Случилась ошибка во время отправки email"
-#: authentication/services/email_service.py:124
+#: authentication/services/email_service.py:128
msgid "Regular users cannot send introductory letters"
msgstr "Обычные пользователи не могут отсылать письма"
-#: authentication/services/email_service.py:158
+#: authentication/services/email_service.py:163
msgid "Regular users cannot send invitation letters"
msgstr "Обычные пользователи не могут отправлять письма для приглашений"
-#: authentication/services/user_services.py:162
+#: authentication/services/user_services.py:167
msgid "No user like this in a database"
msgstr "Такой пользователь отсутствует"
-#: authentication/services/user_services.py:179
+#: authentication/services/user_services.py:184
msgid "token is not provided"
msgstr ""
-#: authentication/services/user_services.py:203
+#: authentication/services/user_services.py:208
msgid "No email token provided"
msgstr "Токен не получен"
-#: authentication/services/user_services.py:207
+#: authentication/services/user_services.py:212
msgid "No token like this in a database"
msgstr "Не найдено такого токена"
-#: authentication/services/user_services.py:213
+#: authentication/services/user_services.py:218
msgid "Passwords do not match"
msgstr "Пароли не совпадают"
-#: authentication/services/user_services.py:251
+#: authentication/services/user_services.py:256
msgid "Current password is wrong"
msgstr "Текущий пароль неверен"
@@ -572,16 +579,16 @@ msgstr "Неизвестный бакет для загрузки"
msgid "The file size cannot exceed %(max_mb_size)d MB"
msgstr "Файл не может быть размером больше %(max_mb_size)d мегабайт"
-#: ml_model/admin.py:74 ml_model/models.py:263 ml_model/models.py:269
-#: ml_model/models.py:301 ml_model/models.py:323
+#: ml_model/admin.py:114 ml_model/models.py:335 ml_model/models.py:341
+#: ml_model/models.py:375 ml_model/models.py:397
msgid "Inference"
msgstr "Инференс"
-#: ml_model/admin.py:75 ml_model/models.py:264 ml_model/models.py:377
+#: ml_model/admin.py:115 ml_model/models.py:336 ml_model/models.py:459
msgid "Inferences"
msgstr "Инференсы"
-#: ml_model/apps.py:8 ml_model/models.py:388
+#: ml_model/apps.py:8 ml_model/models.py:482
msgid "Neuron Models"
msgstr "Нейронные Модели"
@@ -594,351 +601,383 @@ msgstr "Инференс в настоящее время выключен, по
msgid "Parameter %(parameter_name)s not valid, please retry later"
msgstr "Параметр %(parameter_name)s некорректен, повторите попытку позже"
-#: ml_model/models.py:29
+#: ml_model/exceptions.py:26
+#, fuzzy
+#| msgid "Payment Rule"
+msgid "Payment Rule not implemented"
+msgstr "Платежное правило"
+
+#: ml_model/exceptions.py:31
+msgid "Scraper does not exists"
+msgstr ""
+
+#: ml_model/exceptions.py:39
+#, python-format
+msgid "Format is not supported. Supported formats: %(formats)s"
+msgstr "Формат не поддерживается. Поддерживаемые форматы: %(formats)s"
+
+#: ml_model/models.py:31
msgid "Not SVG-pictures not allowed"
msgstr "Нельзя использовать не SVG-картинки"
-#: ml_model/models.py:39
+#: ml_model/models.py:41
#, fuzzy
#| msgid "Tags"
msgid "Tag"
msgstr "Теги"
-#: ml_model/models.py:40 ml_model/models.py:239
+#: ml_model/models.py:42 ml_model/models.py:297
msgid "Tags"
msgstr "Теги"
-#: ml_model/models.py:45 ml_model/models.py:99
-#: reports/models/error_report.py:10
+#: ml_model/models.py:57 ml_model/models.py:86
+msgid "Scraper"
+msgstr ""
+
+#: ml_model/models.py:60
+msgid "Keyword Arguments"
+msgstr ""
+
+#: ml_model/models.py:67
+#, fuzzy
+#| msgid "Runner is missing"
+msgid "Scraper is missing"
+msgstr "Раннер не найден"
+
+#: ml_model/models.py:72 ml_model/models.py:139 ml_model/models.py:234
+#: ml_model/models.py:420 reports/models/error_report.py:10
msgid "Text"
msgstr "Текст"
-#: ml_model/models.py:46
+#: ml_model/models.py:73 ml_model/models.py:235
msgid "File"
msgstr "Файл"
-#: ml_model/models.py:47
+#: ml_model/models.py:74 ml_model/models.py:236
msgid "Embeddings"
msgstr "Эмбеддинги"
-#: ml_model/models.py:49 ml_model/models.py:227
+#: ml_model/models.py:76 ml_model/models.py:285
msgid "ID"
msgstr "ID"
-#: ml_model/exceptions.py:20
-msgid "The model is not responding"
-msgstr "Модель не отвечает"
-
-#: ml_model/models.py:63
+#: ml_model/models.py:99
msgid "Runner"
msgstr "Раннер"
-#: ml_model/models.py:66
+#: ml_model/models.py:102
msgid "Output Type"
msgstr "Тип исходящего контента"
-#: ml_model/models.py:68 ml_model/models.py:241
+#: ml_model/models.py:104 ml_model/models.py:299
msgid "Enabled"
msgstr "Включен"
-#: ml_model/models.py:75
+#: ml_model/models.py:111
msgid "Runner is missing"
msgstr "Раннер не найден"
-#: ml_model/models.py:93 ml_model/models.py:119 ml_model/models.py:162
-#: ml_model/models.py:210 ml_model/models.py:236
+#: ml_model/models.py:133 ml_model/models.py:159 ml_model/models.py:202
+#: ml_model/models.py:268 ml_model/models.py:294
msgid "Deployment"
msgstr "Деплоймент"
-#: ml_model/models.py:94
+#: ml_model/models.py:134
msgid "Deployments"
msgstr "Деплойменты"
-#: ml_model/models.py:100 ml_model/models.py:346 stories/models.py:36
+#: ml_model/models.py:140 ml_model/models.py:421 stories/models.py:36
msgid "Image"
msgstr "Картинка"
-#: ml_model/models.py:101
+#: ml_model/models.py:141
msgid "PDF"
msgstr "PDF"
-#: ml_model/models.py:102
+#: ml_model/models.py:142
msgid "DOCX"
msgstr "DOCX"
-#: ml_model/models.py:103
+#: ml_model/models.py:143
msgid "DOC"
msgstr "DOC"
-#: ml_model/models.py:104
+#: ml_model/models.py:144
msgid "Text File (Notebook)"
msgstr "Текстовый файл (Блокнот)"
-#: ml_model/models.py:105
+#: ml_model/models.py:145
msgid "ZIP Archive"
msgstr "ZIP архив"
-#: ml_model/models.py:106
+#: ml_model/models.py:146
msgid "Audio"
msgstr "Аудио"
-#: ml_model/models.py:112 ml_model/models.py:148 ml_model/models.py:296
-#: ml_model/models.py:371 payments/models/promocode.py:41
+#: ml_model/models.py:152 ml_model/models.py:188 ml_model/models.py:370
+#: ml_model/models.py:449 payments/models/promocode.py:41
msgid "Type"
msgstr "Тип"
-#: ml_model/models.py:114 ml_model/models.py:157 ml_model/models.py:276
+#: ml_model/models.py:154 ml_model/models.py:197 ml_model/models.py:348
msgid "Required"
msgstr "Обязательный"
-#: ml_model/models.py:124
+#: ml_model/models.py:164
#, python-format
msgid "%(input_type)s input of %(deployment_title)s"
msgstr "Входящий поток типа %(input_type)s деплоймента %(deployment_title)s"
-#: ml_model/models.py:130
+#: ml_model/models.py:170
#, fuzzy
#| msgid "Model Input"
msgid "Input"
msgstr "Модель"
-#: ml_model/models.py:131
+#: ml_model/models.py:171
#, fuzzy
#| msgid "Model Inputs"
msgid "Inputs"
msgstr "Входящий поток модели"
-#: ml_model/models.py:137
+#: ml_model/models.py:177
msgid "Integer"
msgstr "Целое число"
-#: ml_model/models.py:138
+#: ml_model/models.py:178
msgid "Float"
msgstr "Вещественное число"
-#: ml_model/models.py:139
+#: ml_model/models.py:179
msgid "String"
msgstr "Строка"
-#: ml_model/models.py:140
+#: ml_model/models.py:180
#, fuzzy
#| msgid "Invoices"
msgid "Choices"
msgstr "Списания"
-#: ml_model/models.py:141
+#: ml_model/models.py:181
msgid "Float range"
msgstr "Вещественный диапазон"
-#: ml_model/models.py:142
+#: ml_model/models.py:182
msgid "Integer range"
msgstr "Целочисленный диапазон"
-#: ml_model/models.py:143
+#: ml_model/models.py:183
msgid "Logical"
msgstr "Логический"
-#: ml_model/models.py:153
+#: ml_model/models.py:193
msgid "Values"
msgstr "Значения"
-#: ml_model/models.py:154
-msgid "These values can contain different interfaces and default value optional"
-msgstr "Значения могут содержать различные интерфейс и, опционально, значение по "
+#: ml_model/models.py:194
+msgid ""
+"These values can contain different interfaces and default value optional"
+msgstr ""
+"Значения могут содержать различные интерфейс и, опционально, значение по "
"умолчанию"
-#: ml_model/models.py:156 ml_model/models.py:275
+#: ml_model/models.py:196 ml_model/models.py:347
msgid "Hidden"
msgstr "Скрытый"
-#: ml_model/models.py:167
+#: ml_model/models.py:208
+msgid "Key \"default\" is required"
+msgstr ""
+
+#: ml_model/models.py:211
#, fuzzy, python-format
#| msgid "Parameter of %(model_title)s"
msgid "Parameter \"%(key)s\" of %(deployment_title)s"
msgstr "Параметр %(model_title)s"
-#: ml_model/models.py:173 ml_model/models.py:272
+#: ml_model/models.py:217 ml_model/models.py:344
msgid "Parameter"
msgstr "Параметр"
-#: ml_model/models.py:174
+#: ml_model/models.py:218
msgid "Parameters"
msgstr "Параметры"
-#: ml_model/models.py:180
+#: ml_model/models.py:224
msgid "Fixed"
msgstr "Фикса"
-#: ml_model/models.py:181
+#: ml_model/models.py:225
msgid "Per generation second"
msgstr "За секунду генерации"
-#: ml_model/models.py:182
+#: ml_model/models.py:226
msgid "Per one text token"
msgstr "За один текстовый токен"
-#: ml_model/models.py:183
+#: ml_model/models.py:227
msgid "Per image pixel"
msgstr "За один пиксель"
-#: ml_model/models.py:186
+#: ml_model/models.py:230
msgid "By input data"
msgstr "По входящим данным"
-#: ml_model/models.py:187
+#: ml_model/models.py:231
msgid "By output data"
msgstr "По исходящим данным"
-#: ml_model/models.py:188
-msgid "By all data"
-msgstr "По всем данным"
-
-#: ml_model/models.py:193
+#: ml_model/models.py:241
msgid "Strategy"
msgstr "Стратегия"
-#: ml_model/models.py:198
+#: ml_model/models.py:248
msgid "Interaction Type"
msgstr "Тип взаимодействия"
-#: ml_model/models.py:203 payments/models/invoice.py:19
+#: ml_model/models.py:255
+#, fuzzy
+#| msgid "Interaction Type"
+msgid "Content Type"
+msgstr "Тип взаимодействия"
+
+#: ml_model/models.py:261 payments/models/invoice.py:19
msgid "Cost"
msgstr "Цена"
-#: ml_model/models.py:204
+#: ml_model/models.py:262
msgid "In RUB, per specified strategy"
msgstr "В рублях, за указанную стратегию"
-#: ml_model/models.py:215
+#: ml_model/models.py:273
#, python-format
-msgid "Payment Rule \"%(strategy)s\"/\"%(interaction_type)s\" of "
+msgid ""
+"Payment Rule \"%(strategy)s\"/\"%(interaction_type)s\" of "
+"%(deployment_title)s"
+msgstr ""
+"Платежное правило \"%(strategy)s\"/\"%(interaction_type)s\" деплоймента "
"%(deployment_title)s"
-msgstr "Платежное правило \"%(strategy)s\"/\"%(interaction_type)s\" деплоймента %(deployment_title)s"
-#: ml_model/models.py:222
+#: ml_model/models.py:280
msgid "Payment Rule"
msgstr "Платежное правило"
-#: ml_model/models.py:223
+#: ml_model/models.py:281
msgid "Payment Rules"
msgstr "Платежные правила"
-#: ml_model/models.py:245
+#: ml_model/models.py:303
msgid "Inference cannot be available when parent Deployment is disabled"
-msgstr "Инференс не может быть доступен, когда родительский Деплоймент выключен"
+msgstr ""
+"Инференс не может быть доступен, когда родительский Деплоймент выключен"
-#: ml_model/models.py:274
+#: ml_model/models.py:346
msgid "Value"
msgstr "Значение"
-#: ml_model/models.py:280
+#: ml_model/models.py:354
msgid "Parameter must be hidden cause parent is hidden"
msgstr "Параметр должен быть скрыт, потому что родительский также скрыт"
-#: ml_model/models.py:282
+#: ml_model/models.py:356
msgid "Parameter must be required cause parent is required"
-msgstr "Параметр должен быть обязательным, потому что родительский также обязателен"
+msgstr ""
+"Параметр должен быть обязательным, потому что родительский также обязателен"
-#: ml_model/models.py:285
+#: ml_model/models.py:359
msgid "Overriden Parameter"
msgstr "Переопределенный параметр"
-#: ml_model/models.py:286
+#: ml_model/models.py:360
msgid "Overriden Parameters"
msgstr "Переопределенные параметры"
-#: ml_model/models.py:292
+#: ml_model/models.py:366
msgid "Addition"
msgstr "Сложение"
-#: ml_model/models.py:293
+#: ml_model/models.py:367
msgid "Multiplication"
msgstr "Умножение"
-#: ml_model/models.py:295
+#: ml_model/models.py:369
msgid "Coefficient"
msgstr "Коэффициент"
-#: ml_model/models.py:308
+#: ml_model/models.py:382
#, python-format
msgid "Payment Bias of %(inference_title)s"
msgstr "Платежный сдвиг %(inference_title)s"
-#: ml_model/models.py:311 ml_model/models.py:312
+#: ml_model/models.py:385 ml_model/models.py:386
msgid "Payment Bias"
msgstr "Платежные сдвиг"
-#: ml_model/models.py:316
+#: ml_model/models.py:390
msgid "Generation time"
msgstr "Время генерации"
-#: ml_model/models.py:317
+#: ml_model/models.py:391
msgid "Tokens cost"
msgstr "Стоимость в токенах"
-#: ml_model/models.py:328
+#: ml_model/models.py:402
#, python-format
msgid "Tracking Record created at %(created_at)s of %(inference_title)s"
msgstr ""
-#: ml_model/models.py:334
+#: ml_model/models.py:408
msgid "Tracking Record"
msgstr "Отслеживающая запись"
-#: ml_model/models.py:335
+#: ml_model/models.py:409
msgid "Tracking Records"
msgstr "Отслеживающие записи"
-#: ml_model/models.py:345
+#: ml_model/models.py:419
msgid "Chat-bots"
msgstr "Чат-боты"
-#: ml_model/models.py:353
+#: ml_model/models.py:422
+msgid "Video"
+msgstr ""
+
+#: ml_model/models.py:423
+msgid "Code"
+msgstr ""
+
+#: ml_model/models.py:430
msgid "Alternative Titles"
msgstr "Альтернативные названия"
-#: ml_model/models.py:359
+#: ml_model/models.py:436
msgid "Fill automatically, don't touch"
msgstr "Заполняется автоматически, не трогать"
-#: ml_model/models.py:368
+#: ml_model/models.py:445
msgid "Avatar"
msgstr "Аватар"
-#: ml_model/models.py:387
+#: ml_model/models.py:452
+#, fuzzy
+#| msgid "Type"
+msgid "Types"
+msgstr "Тип"
+
+#: ml_model/models.py:481
msgid "Neuron Model"
msgstr "Нейронная Модель"
-#: ml_model/selectors/ml_models_selector.py:57
-msgid "no model by this id"
-msgstr "Не найдено моделей по этому ID"
-
-#: ml_model/services/chatgpt.py:125
-msgid "Unable to recognize the image. (Supported formats are PNG, JPG, JPEG)"
-msgstr "Невозможно распознать изображение. (Поддерживаемые форматы: PNG, JPG, JPEG)"
-
-#: ml_model/services/chatgpt.py:150
-msgid "No matching version found"
-msgstr "Соответствующая версия не найдена"
-
-#: ml_model/services/inference.py:49
+#: ml_model/services/inference.py:101
msgid "Payment rules are missing; Inference: {}"
msgstr ""
-#: ml_model/services/inference.py:134
+#: ml_model/services/inference.py:212
#, fuzzy
#| msgid "No matching version found"
msgid "No tracking records found"
msgstr "Соответствующая версия не найдена"
-#: ml_model/services/inference.py:171
-msgid "Unable to predict price"
-msgstr ""
-
-#: ml_model/services/upscaleai.py:80
-msgid "No image given for improving"
-msgstr "Нет изображения для улучшения"
-
#: payments/apps.py:9 payments/models/payment.py:60
msgid "Payments"
msgstr "Платежи"
@@ -1080,10 +1119,6 @@ msgstr "Платежный метод"
msgid "Payment Methods"
msgstr "Платежные методы"
-#: payments/selectors/model_payment_selector.py:25
-msgid "Messages for this model are not registered in a selector"
-msgstr ""
-
#: payments/selectors/payment_plan_selector.py:32
msgid "Business accounts are not allowed to make purchases"
msgstr "Сотрудники не могут производить покупки"
@@ -1196,6 +1231,15 @@ msgstr "Медиа"
msgid "Feed"
msgstr "Шейр пользователей"
+#: tools/chats/apis.py:144 tools/chats/consumers.py:48 tools/media/apis.py:143
+#: tools/public_api/views/base.py:69
+msgid "Enabled inference not found in model"
+msgstr ""
+
+#: tools/chats/consumers.py:25
+msgid "User haven't permissions to connect to orders queue"
+msgstr ""
+
#: tools/chats/models.py:13 tools/public_api/models.py:45
msgid "Is deleted"
msgstr "Удален"
@@ -1209,6 +1253,10 @@ msgstr "Чат %(id)s"
msgid "Chat"
msgstr "Чат"
+#: tools/chats/services/chat.py:21
+msgid "New chat"
+msgstr ""
+
#: tools/public_api/exceptions.py:7
msgid "Upgrade token limit on your api-key"
msgstr "Необходимо повысить лимит токенов у API-ключа"
@@ -1229,23 +1277,46 @@ msgstr "API Ключ"
msgid "API Keys"
msgstr "API Ключи"
-#: tools/public_api/views/base.py:58
+#: tools/public_api/views/base.py:49
msgid "Key limit exceeded"
msgstr "Превышен лимит по ключу"
-#: tools/public_api/views/base.py:63
+#: tools/public_api/views/base.py:55
msgid "Model is blocked by outdating or temporary block, please retry later"
msgstr ""
"Модель заблокирована, т.к закончила обновляться или временно заблокирована, "
"попробуйте позже"
-#: tools/public_api/views/base.py:90
-msgid ""
-"Error occured when create generation. It may cause NSFW-content not allowed, "
-"retry again"
-msgstr ""
-"Случилась ошибка во время генерации. Она может возникать из-за того, что "
-"NSFW-контент запрещен. Попробуйте снова"
+#~ msgid "No business account by this uid at your company"
+#~ msgstr "Такого аккаунта нет в вашей компании"
+
+#~ msgid "The model is not responding"
+#~ msgstr "Модель не отвечает"
+
+#~ msgid "By all data"
+#~ msgstr "По всем данным"
+
+#~ msgid "no model by this id"
+#~ msgstr "Не найдено моделей по этому ID"
+
+#~ msgid ""
+#~ "Unable to recognize the image. (Supported formats are PNG, JPG, JPEG)"
+#~ msgstr ""
+#~ "Невозможно распознать изображение. (Поддерживаемые форматы: PNG, JPG, "
+#~ "JPEG)"
+
+#~ msgid "No matching version found"
+#~ msgstr "Соответствующая версия не найдена"
+
+#~ msgid "No image given for improving"
+#~ msgstr "Нет изображения для улучшения"
+
+#~ msgid ""
+#~ "Error occured when create generation. It may cause NSFW-content not "
+#~ "allowed, retry again"
+#~ msgstr ""
+#~ "Случилась ошибка во время генерации. Она может возникать из-за того, что "
+#~ "NSFW-контент запрещен. Попробуйте снова"
#~ msgid "Category"
#~ msgstr "Категория"
@@ -1298,6 +1369,9 @@ msgstr ""
#~ msgid "Rate"
#~ msgstr "Ставка"
-
-msgid "You cannot change the password of an unconfirmed e-mail user."
-msgstr "Вы не можете изменить пароль неподтвержденного по e-mail пользователя."
+#~ msgid ""
+#~ "Model version not found or does not exist. Available versions: "
+#~ "%(available_versions)s"
+#~ msgstr ""
+#~ "Версия модели не найдена или не существует. Доступные версии: "
+#~ "%(available_versions)s"
@@ -1,6 +1,5 @@
# Generated by Django 5.0.11 on 2025-05-04 11:29
-from copy import deepcopy
import django.db.models.deletion
from django.db import migrations, models
@@ -48,30 +47,36 @@ def migrate_fks_versions_to_deployments(apps, schema_editor):
ModelInput = apps.get_model('ml_model', 'ModelInput')
Deployment = apps.get_model('ml_model', 'Deployment')
- old_inputs = ModelInput.objects.all()
inputs = []
- ModelInput.objects.all().delete()
- old_parameters = ModelParameter.objects.all()
parameters = []
- ModelParameter.objects.all().delete()
- for input in old_inputs:
- for version in input.versions:
- new_input = deepcopy(input)
- new_input.id = None
- new_input.inference = Deployment.objects.get(slug=version.slug)
- inputs.append(new_input)
+ for input in ModelInput.objects.all():
+ for version in input.versions.all():
+ inputs.append(ModelInput(deployment=Deployment.objects.get(slug=version.slug), type=input.type, model=input.model, required=input.required))
+
+ for parameter in ModelParameter.objects.all():
+ for version in parameter.versions.all():
+ parameters.append(
+ ModelParameter(
+ deployment=Deployment.objects.get(slug=version.slug),
+ name=parameter.name,
+ model=parameter.model,
+ description=parameter.description,
+ order=parameter.order,
+ key=parameter.key,
+ type=parameter.type,
+ values=parameter.values,
+ hidden=parameter.hidden,
+ required=parameter.required
+ )
+ )
- for parameter in old_parameters:
- for version in parameter.versions:
- new_parameter = deepcopy(parameter)
- new_parameter.id = None
- new_parameter.inference = Deployment.objects.get(slug=version.slug)
- parameters.append(new_parameter)
+ ModelParameter.objects.all().delete()
+ ModelInput.objects.all().delete()
- ModelInput.objects.bulk_create(inputs)
- ModelParameter.objects.bulk_create(parameters)
+ ModelInput.objects.bulk_create(inputs, ignore_conflicts=True)
+ ModelParameter.objects.bulk_create(parameters, ignore_conflicts=True)
class Migration(migrations.Migration):
@@ -88,7 +93,8 @@ class Migration(migrations.Migration):
),
migrations.RunPython(
code=migrate_nm_type,
- reverse_code=migrations.RunPython.noop
+ reverse_code=migrations.RunPython.noop,
+ atomic=True
),
migrations.RemoveField(
model_name='neuronmodel',
@@ -128,187 +134,4 @@ class Migration(migrations.Migration):
code=migrate_fks_versions_to_deployments,
reverse_code=migrations.RunPython.noop
),
- migrations.AlterField(
- model_name='modelinput',
- name='deployment',
- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='deployment_inputs', to='ml_model.deployment', verbose_name='Deployment'),
- ),
- migrations.AlterField(
- model_name='modelparameter',
- name='deployment',
- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='deployment_parameters', to='ml_model.deployment', verbose_name='Deployment'),
- ),
- migrations.AlterField(
- model_name='modelpaymentrule',
- name='inference',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_%(class)s', to='ml_model.inference', verbose_name='Inference'),
- ),
- migrations.AlterField(
- model_name='modelstat',
- name='inference',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_tracking_records', to='ml_model.inference', verbose_name='Inference'),
- ),
- migrations.DeleteModel(
- name='ModelCategory',
- ),
- migrations.RemoveField(
- model_name='modelinput',
- name='model',
- ),
- migrations.RemoveField(
- model_name='modelinput',
- name='versions',
- ),
- migrations.RemoveField(
- model_name='modelparameter',
- name='model',
- ),
- migrations.RemoveField(
- model_name='modelparameter',
- name='versions',
- ),
- migrations.RemoveField(
- model_name='modelpaymentrule',
- name='model',
- ),
- migrations.RemoveField(
- model_name='modelpaymentrule',
- name='versions',
- ),
- migrations.RemoveField(
- model_name='modelstat',
- name='model',
- ),
- migrations.RemoveField(
- model_name='neuronmodel',
- name='tags',
- ),
- migrations.RemoveField(
- model_name='modelversion',
- name='model',
- ),
- migrations.DeleteModel(
- name='ModelVersion',
- ),
- migrations.RenameModel(
- old_name='ModelTag',
- new_name='Tag',
- ),
- migrations.RenameModel(
- old_name='ModelInput',
- new_name='Input',
- ),
- migrations.RenameModel(
- old_name='ModelParameter',
- new_name='Parameter',
- ),
- migrations.RenameModel(
- old_name='ModelPaymentRule',
- new_name='PaymentRule',
- ),
- migrations.RenameModel(
- old_name='ModelStat',
- new_name='TrackingRecord',
- ),
- migrations.CreateModel(
- name='PaymentBias',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('coefficient', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Coefficient')),
- ('type', models.CharField(choices=[('addition', 'Addition'), ('multiplication', 'Multiplication')], max_length=32, verbose_name='Type')),
- ('inference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_payment_biases', to='ml_model.inference', verbose_name='Inference')),
- ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
- ],
- options={
- 'ordering': ('order',),
- 'verbose_name': 'Payment Bias',
- 'verbose_name_plural': 'Payment Bias',
- },
- ),
- migrations.DeleteModel(
- name='PaymentRule',
- ),
- migrations.AlterModelOptions(
- name='input',
- options={'verbose_name': 'Input', 'verbose_name_plural': 'Inputs'},
- ),
- migrations.AlterModelOptions(
- name='parameter',
- options={'verbose_name': 'Parameter', 'verbose_name_plural': 'Parameters'},
- ),
- migrations.AlterModelOptions(
- name='tag',
- options={'verbose_name': 'Tag', 'verbose_name_plural': 'Tags'},
- ),
- migrations.AlterModelOptions(
- name='trackingrecord',
- options={'ordering': ('-created_at',), 'verbose_name': 'Tracking Record', 'verbose_name_plural': 'Tracking Records'},
- ),
- migrations.RemoveField(
- model_name='parameter',
- name='order',
- ),
- migrations.AddField(
- model_name='inference',
- name='tags',
- field=models.ManyToManyField(blank=True, related_name='inferences_tags', to='ml_model.tag', verbose_name='Tags'),
- ),
- migrations.AlterField(
- model_name='inference',
- name='name',
- field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Name'),
- ),
- migrations.AlterField(
- model_name='parameter',
- name='type',
- field=models.CharField(choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String'), ('choices', 'Choices'), ('floatrange', 'Float range'), ('intrange', 'Integer range'), ('bool', 'Logical')], max_length=40, verbose_name='Type'),
- ),
- migrations.AlterField(
- model_name='trackingrecord',
- name='created_at',
- field=models.DateTimeField(auto_now_add=True, verbose_name='Created at'),
- ),
- migrations.AlterField(
- model_name='trackingrecord',
- name='generation_time',
- field=models.DurationField(verbose_name='Generation time'),
- ),
- migrations.AlterField(
- model_name='trackingrecord',
- name='tokens_cost',
- field=models.DecimalField(decimal_places=10, max_digits=50, verbose_name='Tokens cost'),
- ),
- migrations.CreateModel(
- name='OverridenParameter',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('value', models.JSONField(verbose_name='Value')),
- ('hidden', models.BooleanField(default=False, verbose_name='Hidden')),
- ('required', models.BooleanField(default=False, verbose_name='Required')),
- ('inference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_parameters', to='ml_model.inference', verbose_name='Inference')),
- ('parameter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parameters_overriden', to='ml_model.parameter', verbose_name='Parameter')),
- ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
- ],
- options={
- 'ordering': ('order',),
- 'verbose_name': 'Overriden Parameter',
- 'verbose_name_plural': 'Overriden Parameters',
- 'unique_together': {('parameter', 'inference')},
- },
- ),
- migrations.CreateModel(
- name='PaymentRule',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('strategy', models.CharField(choices=[('fixed', 'Fixed'), ('per-second', 'Per generation second'), ('per-text-token', 'Per one text token'), ('per-pixel', 'Per image pixel')], max_length=32, verbose_name='Strategy')),
- ('cost', models.DecimalField(decimal_places=8, help_text='In RUB, per specified strategy', max_digits=10, verbose_name='Cost')),
- ('deployment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='deployment_payment_rules', to='ml_model.deployment', verbose_name='Deployment')),
- ('content_type', models.CharField(blank=True, choices=[('text', 'Text'), ('file', 'File'), ('embeddings', 'Embeddings')], max_length=32, null=True, verbose_name='Content Type')),
- ('interaction_type', models.CharField(blank=True, choices=[('input', 'By input data'), ('output', 'By output data')], max_length=32, null=True, verbose_name='Interaction Type')),
- ],
- options={
- 'verbose_name': 'Payment Rule',
- 'verbose_name_plural': 'Payment Rules',
- },
- ),
]
@@ -1,8 +1,15 @@
# Generated by Django 5.0.11 on 2025-05-27 07:29
+import django
from django.db import migrations, models
+def cleanup_payment_rules(apps, schema_editor):
+ ModelPaymentRule = apps.get_model('ml_model', 'ModelPaymentRule')
+ ModelPaymentRule.objects.all().delete()
+def cleanup_tracking_records(apps, schema_editor):
+ ModelStat = apps.get_model('ml_model', 'ModelStat')
+ ModelStat.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
@@ -10,9 +17,12 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.AlterField(
- model_name='deployment',
- name='runner_import_path',
- field=models.CharField(choices=[('ml_model.runners:FalAIRunner', 'FalAI'), ('ml_model.runners:OpenAIGPTRunner', 'OpenAIGPT'), ('ml_model.runners:OpenrouterRunner', 'Openrouter'), ('ml_model.runners:ReplicateAudioRunner', 'ReplicateAudio'), ('ml_model.runners:ReplicateImageRunner', 'ReplicateImage'), ('ml_model.runners:ReplicateTextRunner', 'ReplicateText'), ('ml_model.runners:ReplicateVideoRunner', 'ReplicateVideo')], max_length=100, verbose_name='Runner'),
+ migrations.RunPython(
+ code=cleanup_tracking_records,
+ reverse_code=migrations.RunPython.noop
+ ),
+ migrations.RunPython(
+ code=cleanup_payment_rules,
+ reverse_code=migrations.RunPython.noop
),
]
@@ -0,0 +1,201 @@
+# Generated by Django 5.0.11 on 2025-05-27 21:42
+
+import django
+from django.db import migrations, models
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('ml_model', '0056_alter_deployment_runner_import_path'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deployment',
+ name='runner_import_path',
+ field=models.CharField(choices=[('ml_model.runners:FalAIRunner', 'FalAI'), ('ml_model.runners:OpenAIGPTRunner', 'OpenAIGPT'), ('ml_model.runners:OpenrouterRunner', 'Openrouter'), ('ml_model.runners:ReplicateAudioRunner', 'ReplicateAudio'), ('ml_model.runners:ReplicateImageRunner', 'ReplicateImage'), ('ml_model.runners:ReplicateTextRunner', 'ReplicateText'), ('ml_model.runners:ReplicateVideoRunner', 'ReplicateVideo')], max_length=100, verbose_name='Runner'),
+ ),
+ migrations.AlterField(
+ model_name='modelinput',
+ name='deployment',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='deployment_inputs', to='ml_model.deployment', verbose_name='Deployment'),
+ ),
+ migrations.AlterField(
+ model_name='modelparameter',
+ name='deployment',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='deployment_parameters', to='ml_model.deployment', verbose_name='Deployment'),
+ ),
+ migrations.AlterField(
+ model_name='modelpaymentrule',
+ name='inference',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_paymentrules', to='ml_model.inference', verbose_name='Inference'),
+ ),
+ migrations.AlterField(
+ model_name='modelstat',
+ name='inference',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_tracking_records', to='ml_model.inference', verbose_name='Inference'),
+ ),
+ migrations.DeleteModel(
+ name='ModelCategory',
+ ),
+ migrations.RemoveField(
+ model_name='modelinput',
+ name='model',
+ ),
+ migrations.RemoveField(
+ model_name='modelinput',
+ name='versions',
+ ),
+ migrations.RemoveField(
+ model_name='modelparameter',
+ name='model',
+ ),
+ migrations.RemoveField(
+ model_name='modelparameter',
+ name='versions',
+ ),
+ migrations.RemoveField(
+ model_name='modelpaymentrule',
+ name='model',
+ ),
+ migrations.RemoveField(
+ model_name='modelpaymentrule',
+ name='versions',
+ ),
+ migrations.RemoveField(
+ model_name='modelstat',
+ name='model',
+ ),
+ migrations.RemoveField(
+ model_name='neuronmodel',
+ name='tags',
+ ),
+ migrations.RemoveField(
+ model_name='modelversion',
+ name='model',
+ ),
+ migrations.DeleteModel(
+ name='ModelVersion',
+ ),
+ migrations.RenameModel(
+ old_name='ModelTag',
+ new_name='Tag',
+ ),
+ migrations.RenameModel(
+ old_name='ModelInput',
+ new_name='Input',
+ ),
+ migrations.RenameModel(
+ old_name='ModelParameter',
+ new_name='Parameter',
+ ),
+ migrations.RenameModel(
+ old_name='ModelPaymentRule',
+ new_name='PaymentRule',
+ ),
+ migrations.RenameModel(
+ old_name='ModelStat',
+ new_name='TrackingRecord',
+ ),
+ migrations.CreateModel(
+ name='PaymentBias',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('coefficient', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Coefficient')),
+ ('type', models.CharField(choices=[('addition', 'Addition'), ('multiplication', 'Multiplication')], max_length=32, verbose_name='Type')),
+ ('inference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_payment_biases', to='ml_model.inference', verbose_name='Inference')),
+ ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
+ ],
+ options={
+ 'ordering': ('order',),
+ 'verbose_name': 'Payment Bias',
+ 'verbose_name_plural': 'Payment Bias',
+ },
+ ),
+ migrations.DeleteModel(
+ name='PaymentRule',
+ ),
+ migrations.AlterModelOptions(
+ name='input',
+ options={'verbose_name': 'Input', 'verbose_name_plural': 'Inputs'},
+ ),
+ migrations.AlterModelOptions(
+ name='parameter',
+ options={'verbose_name': 'Parameter', 'verbose_name_plural': 'Parameters'},
+ ),
+ migrations.AlterModelOptions(
+ name='tag',
+ options={'verbose_name': 'Tag', 'verbose_name_plural': 'Tags'},
+ ),
+ migrations.AlterModelOptions(
+ name='trackingrecord',
+ options={'ordering': ('-created_at',), 'verbose_name': 'Tracking Record', 'verbose_name_plural': 'Tracking Records'},
+ ),
+ migrations.RemoveField(
+ model_name='parameter',
+ name='order',
+ ),
+ migrations.AddField(
+ model_name='inference',
+ name='tags',
+ field=models.ManyToManyField(blank=True, related_name='inferences_tags', to='ml_model.tag', verbose_name='Tags'),
+ ),
+ migrations.AlterField(
+ model_name='inference',
+ name='name',
+ field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Name'),
+ ),
+ migrations.AlterField(
+ model_name='parameter',
+ name='type',
+ field=models.CharField(choices=[('int', 'Integer'), ('float', 'Float'), ('str', 'String'), ('choices', 'Choices'), ('floatrange', 'Float range'), ('intrange', 'Integer range'), ('bool', 'Logical')], max_length=40, verbose_name='Type'),
+ ),
+ migrations.AlterField(
+ model_name='trackingrecord',
+ name='created_at',
+ field=models.DateTimeField(auto_now_add=True, verbose_name='Created at'),
+ ),
+ migrations.AlterField(
+ model_name='trackingrecord',
+ name='generation_time',
+ field=models.DurationField(verbose_name='Generation time'),
+ ),
+ migrations.AlterField(
+ model_name='trackingrecord',
+ name='tokens_cost',
+ field=models.DecimalField(decimal_places=10, max_digits=50, verbose_name='Tokens cost'),
+ ),
+ migrations.CreateModel(
+ name='OverridenParameter',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('value', models.JSONField(verbose_name='Value')),
+ ('hidden', models.BooleanField(default=False, verbose_name='Hidden')),
+ ('required', models.BooleanField(default=False, verbose_name='Required')),
+ ('inference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inference_parameters', to='ml_model.inference', verbose_name='Inference')),
+ ('parameter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parameters_overriden', to='ml_model.parameter', verbose_name='Parameter')),
+ ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
+ ],
+ options={
+ 'ordering': ('order',),
+ 'verbose_name': 'Overriden Parameter',
+ 'verbose_name_plural': 'Overriden Parameters',
+ 'unique_together': {('parameter', 'inference')},
+ },
+ ),
+ migrations.CreateModel(
+ name='PaymentRule',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('strategy', models.CharField(choices=[('fixed', 'Fixed'), ('per-second', 'Per generation second'), ('per-text-token', 'Per one text token'), ('per-pixel', 'Per image pixel')], max_length=32, verbose_name='Strategy')),
+ ('cost', models.DecimalField(decimal_places=8, help_text='In RUB, per specified strategy', max_digits=10, verbose_name='Cost')),
+ ('deployment', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='deployment_payment_rules', to='ml_model.deployment', verbose_name='Deployment')),
+ ('content_type', models.CharField(blank=True, choices=[('text', 'Text'), ('file', 'File'), ('embeddings', 'Embeddings')], max_length=32, null=True, verbose_name='Content Type')),
+ ('interaction_type', models.CharField(blank=True, choices=[('input', 'By input data'), ('output', 'By output data')], max_length=32, null=True, verbose_name='Interaction Type')),
+ ],
+ options={
+ 'verbose_name': 'Payment Rule',
+ 'verbose_name_plural': 'Payment Rules',
+ },
+ ),
+ ]
@@ -3,6 +3,7 @@ from ml_model.runners.openrouter import OpenrouterRunner
from ml_model.runners.replicate import (
ReplicateAudioRunner,
ReplicateImageRunner,
+ ReplicateIconicRunner,
ReplicateTextRunner,
ReplicateVideoRunner,
)
@@ -14,6 +15,7 @@ __all__ = [
'ReplicateTextRunner',
'ReplicateAudioRunner',
'ReplicateImageRunner',
+ 'ReplicateIconicRunner',
'ReplicateVideoRunner',
'FalAIRunner'
]
@@ -21,10 +21,14 @@ class FalAIRunner(BaseRunner):
scrape_results: list[StringIO] | list[BytesIO] = [],
) -> Generator[str, Any, None]:
try:
+ image_size = {
+ "width": parameters.pop('width'),
+ "height": parameters.pop('height')
+ }
version = parameters.pop('model')
model_owner = parameters.pop('model_owner')
official = version and model_owner
- payload = {'prompt': content, **parameters}
+ payload = {'prompt': content, 'image_size': image_size, **parameters}
if not official:
payload.update({'model_version': version})
with httpx.Client(
@@ -42,6 +42,7 @@ class OpenAICompatibleRunner(BaseRunner, ABC):
'stream': True,
**parameters,
}
+ logger.info(payload)
for result in scrape_results:
if isinstance(result, StringIO):
payload['messages'].append(
@@ -79,6 +80,7 @@ class OpenAICompatibleRunner(BaseRunner, ABC):
try:
with client.stream('POST', '/chat/completions', json=payload) as stream:
stream_content = stream.iter_text()
+ logger.info(stream_content)
if stream.status_code >= 400:
raw = ''.join([chunk for chunk in stream_content])
@@ -108,6 +110,8 @@ class OpenAICompatibleRunner(BaseRunner, ABC):
yield content
except json.decoder.JSONDecodeError:
continue
+ except httpx.ConnectError:
+ continue
except httpx.TimeoutException as exc:
logger.exception(exc)
raise Exception('Timeout happened')
@@ -1,8 +1,11 @@
+import logging
+
from django.conf import settings
from ml_model.exceptions import ParameterNotValid
from ml_model.runners.openai import OpenAICompatibleRunner
+logger = logging.getLogger(__name__)
class OpenrouterRunner(OpenAICompatibleRunner):
BASE_URL = 'https://openrouter.ai/api/v1'
@@ -13,6 +16,7 @@ class OpenrouterRunner(OpenAICompatibleRunner):
@classmethod
def map_errors(cls, error):
if isinstance(error, str) or error['code'] == 400:
+ logger.error(error)
raise Exception('Unexpected error')
elif error['message'].endswith('is not a valid model ID'):
raise ParameterNotValid('model')
@@ -1,4 +1,5 @@
import base64
+import logging
from abc import ABC, abstractmethod
from io import BytesIO, StringIO
from typing import TYPE_CHECKING, Any, Iterable
@@ -12,6 +13,7 @@ from ml_model.runners.base import BaseRunner
if TYPE_CHECKING:
from messages.models import Message
+logger = logging.getLogger(__name__)
class ReplicateBaseRunner(BaseRunner, ABC):
@classmethod
@@ -131,6 +133,7 @@ class ReplicateImageRunner(BaseRunner):
model_owner = parameters.pop('model_owner')
official = version and model_owner
payload = {'input': {parameters.pop('prompt_key', None) or 'prompt': content, **parameters}}
+ logger.info(official)
if not official:
payload.update({'version': version})
if file:
@@ -152,9 +155,12 @@ class ReplicateImageRunner(BaseRunner):
resp = client.post(
f'/models/{model_owner}/{version}/predictions' if official else '/predictions', json=payload
)
+ logger.info(resp)
data = resp.json()
+ logger.info(data)
stream_url = data['urls']['stream']
get_url = data['urls']['get']
+ logger.info(stream_url, get_url)
with client.stream(
'GET', stream_url, headers={'Accept': 'text/event-stream', 'Cache-Control': 'no-store'}
) as stream:
@@ -168,6 +174,7 @@ class ReplicateImageRunner(BaseRunner):
if content_length == 0:
resp = client.get(get_url)
data = resp.json()
+ logger.info(data)
if data['status'] == 'failed':
raise Exception('Model failed generation')
@@ -180,3 +187,56 @@ class ReplicateVideoRunner(BaseRunner):
class ReplicateAudioRunner(BaseRunner):
@classmethod
def generate(cls, content=None, file=None, parameters={}, history=[], scrape_results=[]): ...
+
+
+class ReplicateIconicRunner(BaseRunner):
+ @classmethod
+ def generate(cls, content=None, file=None, parameters={}, history=[], scrape_results=[]):
+ version = parameters.pop('version')
+ model_owner = parameters.pop('model_owner')
+ official = version and model_owner
+ payload = {'input': {parameters.pop('prompt_key', None) or 'prompt': content, **parameters}}
+ logger.info(official)
+ if not official:
+ payload.update({'version': version})
+ if file:
+ kind = filetype.guess(file.read(20))
+ format = 'jpeg' if kind.extension == 'jpg' else kind.extension
+ if format in ('jpeg', 'png'):
+ mime = kind.mime if kind else 'application/octet-stream'
+ payload['input'][parameters.pop('file_key', None) or 'file'] = (
+ f'data:{mime};base64,{base64.b64encode(file.read()).decode("utf-8")}'
+ )
+ with httpx.Client(
+ base_url='https://api.replicate.com/v1',
+ headers={
+ 'Authorization': f'Bearer {settings.REPLICATE_API_KEY}',
+ 'Content-Type': 'application/json',
+ },
+ timeout=None,
+ ) as client:
+ resp = client.post(
+ f'/models/{model_owner}/{version}/predictions' if official else '/predictions', json=payload
+ )
+ logger.info(resp)
+ data = resp.json()
+ logger.info(data)
+ stream_url = data['urls']['stream']
+ get_url = data['urls']['get']
+ logger.info(stream_url, get_url)
+ with client.stream(
+ 'GET', stream_url, headers={'Accept': 'text/event-stream', 'Cache-Control': 'no-store'}
+ ) as stream:
+ content_length = 0
+ for chunk in stream.iter_text():
+ chunk = chunk.strip().split('\n')
+ chunk = chunk[0] if content_length > 0 else chunk[-1][len('data:') :].strip()
+ content_length += len(chunk)
+ yield chunk
+
+ if content_length == 0:
+ resp = client.get(get_url)
+ data = resp.json()
+ logger.info(data)
+ if data['status'] == 'failed':
+ raise Exception('Model failed generation')
@@ -29,3 +29,11 @@ class PaymentRuleNotImplemented(Exception):
class ScraperDoesNotExists(Exception):
def __str__(self):
return _('Scraper does not exists')
+
+
+class FormatNotSupported(Exception):
+ def __init__(self, formats: str) -> None:
+ self.formats = formats
+
+ def __str__(self) -> str:
+ return _('Format is not supported. Supported formats: %(formats)s') % {'formats': self.formats}
@@ -1,3 +1,4 @@
+import asyncio
import logging
from asgiref.sync import async_to_sync
@@ -7,6 +7,9 @@ services:
dockerfile: Dockerfile
volumes:
- static:/code/static
+ networks:
+ - default
+ - infrastructure
command:
- /bin/sh
- -c
@@ -51,7 +54,7 @@ services:
command:
- /bin/sh
- -c
- - python manage.py migrate
+ - python manage.py migrate --verbosity 3
env_file:
- $ENV
@@ -59,6 +62,8 @@ services:
restart: unless-stopped
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
command: celery -A backend worker -l INFO --concurrency 8
+ networks:
+ - default
env_file:
- $ENV
environment:
@@ -70,6 +75,8 @@ services:
restart: unless-stopped
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
command: celery -A backend beat -l INFO
+ networks:
+ - default
env_file:
- $ENV
depends_on:
@@ -101,17 +108,22 @@ services:
cache-mdb:
image: redis:alpine
+ networks:
+ - default
restart: unless-stopped
celery-mdb:
image: redis:alpine
+ networks:
+ - default
restart: unless-stopped
networks:
- default:
+ infrastructure:
name: infrastructure
external: true
-
+ default: {}
+
volumes:
static:
name: "backend-static"
@@ -10,10 +10,6 @@ services:
tags:
- $CI_REGISTRY_IMAGE:latest
- $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- cache_from:
- - type=registry,ref=$CI_REGISTRY_IMAGE/cache,ignore-error=true
- cache_to:
- - type=registry,ref=$CI_REGISTRY_IMAGE/cache,mode=max,ignore-error=true
volumes:
- static:/code/static
command: