diff --git a/.env b/.env index 369204b..ff9fabb 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ BOT_TOKEN=8495170789:AAHyjjhHwwVtd9_ROnjHqPHRdnmyVr1aeaY GEMINI_API_KEY=AIzaSyAHzDYhgjOqZZnvOnOFRGaSkKu4OAN3kZE MONGO_HOST=mongodb://admin:super_secure_password@31.59.58.220:27017/ ADMIN_ID=567047 -MINIO_ENDPOINT=https://minio.luminic.space +MINIO_ENDPOINT=http://31.59.58.220:9000 MINIO_ACCESS_KEY=admin MINIO_SECRET_KEY=SuperSecretPassword123! MINIO_BUCKET=ai-char diff --git a/api/endpoints/__pycache__/assets_router.cpython-313.pyc b/api/endpoints/__pycache__/assets_router.cpython-313.pyc index 467c209..79f5d02 100644 Binary files a/api/endpoints/__pycache__/assets_router.cpython-313.pyc and b/api/endpoints/__pycache__/assets_router.cpython-313.pyc differ diff --git a/api/endpoints/assets_router.py b/api/endpoints/assets_router.py index b469e34..b9b2ae5 100644 --- a/api/endpoints/assets_router.py +++ b/api/endpoints/assets_router.py @@ -9,7 +9,7 @@ from starlette.requests import Request from starlette.responses import Response, JSONResponse from api.models.AssetDTO import AssetsResponse, AssetResponse -from models.Asset import Asset, AssetType +from models.Asset import Asset, AssetType, AssetContentType from repos.dao import DAO from api.dependency import get_dao import asyncio @@ -105,7 +105,8 @@ async def upload_asset( asset = Asset( name=file.filename or "upload", - type=AssetType.IMAGE, + type=AssetType.UPLOADED, + content_type=AssetContentType.IMAGE, linked_char_id=linked_char_id, data=data, thumbnail=thumbnail_bytes @@ -119,6 +120,7 @@ async def upload_asset( id=asset.id, name=asset.name, type=asset.type.value if hasattr(asset.type, "value") else asset.type, + content_type=asset.content_type.value if hasattr(asset.content_type, "value") else asset.content_type, linked_char_id=asset.linked_char_id, created_at=asset.created_at, url=asset.url @@ -145,7 +147,7 @@ async def regenerate_thumbnails(dao: DAO = Depends(get_dao)): updated = 0 for asset in assets: - if asset.type == AssetType.IMAGE and asset.data : + if asset.content_type == AssetContentType.IMAGE and asset.data : try: thumb = await asyncio.to_thread(create_thumbnail, asset.data) if thumb: diff --git a/api/models/AssetDTO.py b/api/models/AssetDTO.py index 9fefd7c..8d46977 100644 --- a/api/models/AssetDTO.py +++ b/api/models/AssetDTO.py @@ -9,7 +9,8 @@ from models.Asset import Asset class AssetResponse(BaseModel): id: str name: str - type: str + type: str # uploaded / generated + content_type: str # image / prompt linked_char_id: Optional[str] = None created_at: datetime url: Optional[str] = None diff --git a/api/models/__pycache__/AssetDTO.cpython-313.pyc b/api/models/__pycache__/AssetDTO.cpython-313.pyc index 2bfba91..8885db4 100644 Binary files a/api/models/__pycache__/AssetDTO.cpython-313.pyc and b/api/models/__pycache__/AssetDTO.cpython-313.pyc differ diff --git a/api/service/__pycache__/generation_service.cpython-313.pyc b/api/service/__pycache__/generation_service.cpython-313.pyc index 5abea4a..041fbd7 100644 Binary files a/api/service/__pycache__/generation_service.cpython-313.pyc and b/api/service/__pycache__/generation_service.cpython-313.pyc differ diff --git a/api/service/generation_service.py b/api/service/generation_service.py index a168442..7b31ba9 100644 --- a/api/service/generation_service.py +++ b/api/service/generation_service.py @@ -11,7 +11,7 @@ from adapters.Exception import GoogleGenerationException from adapters.google_adapter import GoogleAdapter from api.models.GenerationRequest import GenerationRequest, GenerationResponse # Импортируйте ваши модели DAO, Asset, Generation корректно -from models.Asset import Asset, AssetType +from models.Asset import Asset, AssetType, AssetContentType from models.Generation import Generation, GenerationStatus from models.enums import AspectRatios, Quality, GenType from repos.dao import DAO @@ -174,7 +174,7 @@ class GenerationService: # Извлекаем данные (bytes) из ассетов для отправки в Gemini for asset in reference_assets: - if asset.type != AssetType.IMAGE: + if asset.content_type != AssetContentType.IMAGE: continue img_data = None @@ -249,7 +249,8 @@ class GenerationService: new_asset = Asset( name=f"Generated_{generation.linked_character_id}", - type=AssetType.IMAGE, + type=AssetType.GENERATED, + content_type=AssetContentType.IMAGE, linked_char_id=generation.linked_character_id, data=None, # Not storing bytes in DB anymore minio_object_name=filename, diff --git a/models/Asset.py b/models/Asset.py index 3c982e0..81a34bd 100644 --- a/models/Asset.py +++ b/models/Asset.py @@ -2,18 +2,23 @@ from datetime import datetime, UTC from enum import Enum from typing import Optional, Any, List -from pydantic import BaseModel, computed_field, Field +from pydantic import BaseModel, computed_field, Field, model_validator -class AssetType(str, Enum): +class AssetContentType(str, Enum): IMAGE = 'image' PROMPT = 'prompt' +class AssetType(str, Enum): + UPLOADED = 'uploaded' + GENERATED = 'generated' + class Asset(BaseModel): id: Optional[str] = None name: str - type: AssetType + type: AssetType = AssetType.GENERATED + content_type: AssetContentType = AssetContentType.IMAGE linked_char_id: Optional[str] = None data: Optional[bytes] = None tg_doc_file_id: Optional[str] = None @@ -26,6 +31,33 @@ class Asset(BaseModel): created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) + @model_validator(mode='before') + @classmethod + def check_legacy_type(cls, data: Any) -> Any: + if isinstance(data, dict): + # Если поле type содержит старые значения ("image", "prompt"), + # переносим их в content_type, а type ставим по умолчанию (GENERATED) + # или пытаемся угадать. + # Но по задаче мы дефолтим в GENERATED, и script'ом поправим. + + raw_type = data.get('type') + if raw_type in ['image', 'prompt']: + data['content_type'] = raw_type + # Если в базе нет нового поля type, оно встанет в default=GENERATED + # Чтобы не вызывало ошибку валидации AssetType, меняем его или удаляем, + # полагаясь на default. + # Но если мы просто удалим, поле type примет дефолтное значение. + # Однако, если мы хотим явно отличить, можно ничего не делать, + # но тогда валидация поля `type` упадет, т.к. "image" != "generated". + # Поэтому удаляем старое значение из type, чтобы сработал дефолт. + if 'type' in data: + del data['type'] + + # Если content_type нет в данных (легаси), пытаемся его восстановить из удалённого type + # (выше мы его переложили). + + return data + # --- CALCULATED FIELD --- @computed_field def url(self) -> str: diff --git a/models/__pycache__/Asset.cpython-313.pyc b/models/__pycache__/Asset.cpython-313.pyc index 1459742..f1873ef 100644 Binary files a/models/__pycache__/Asset.cpython-313.pyc and b/models/__pycache__/Asset.cpython-313.pyc differ diff --git a/routers/__pycache__/char_router.cpython-313.pyc b/routers/__pycache__/char_router.cpython-313.pyc index 6356e12..478ec0b 100644 Binary files a/routers/__pycache__/char_router.cpython-313.pyc and b/routers/__pycache__/char_router.cpython-313.pyc differ diff --git a/routers/__pycache__/gen_router.cpython-313.pyc b/routers/__pycache__/gen_router.cpython-313.pyc index bb98d46..6791fb9 100644 Binary files a/routers/__pycache__/gen_router.cpython-313.pyc and b/routers/__pycache__/gen_router.cpython-313.pyc differ diff --git a/routers/char_router.py b/routers/char_router.py index 7ec7c71..0d13acc 100644 --- a/routers/char_router.py +++ b/routers/char_router.py @@ -8,7 +8,7 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import * from aiogram import Router, F, Bot -from models.Asset import Asset, AssetType +from models.Asset import Asset, AssetType, AssetContentType from models.Character import Character from repos.dao import DAO @@ -74,7 +74,7 @@ async def new_char_bio(message: Message, state: FSMContext, dao: DAO, bot: Bot): file_bytes = await bot.download_file(file_info.file_path) file_io = file_bytes.read() avatar_asset = await dao.assets.create_asset( - Asset(name="avatar.png", type=AssetType.IMAGE, linked_char_id=str(char.id), data=file_io, + Asset(name="avatar.png", type=AssetType.UPLOADED, content_type=AssetContentType.IMAGE, linked_char_id=str(char.id), data=file_io, tg_doc_file_id=file_id)) char.avatar_image = avatar_asset.link # Отправляем подтверждение diff --git a/routers/gen_router.py b/routers/gen_router.py index fdcfe6a..86461a2 100644 --- a/routers/gen_router.py +++ b/routers/gen_router.py @@ -14,7 +14,7 @@ from aiogram.types import * import keyboards from adapters.google_adapter import GoogleAdapter -from models.Asset import Asset, AssetType +from models.Asset import Asset, AssetType, AssetContentType from models.Character import Character from models.enums import AspectRatios, Quality, GenType from repos.dao import DAO @@ -50,7 +50,7 @@ async def generate_image_cmd(message: Message, state: FSMContext, dao: DAO, gemi gemini=gemini) await wait_msg.delete() doc = await message.answer_document(res[0], caption="Generated result 💫") - await dao.assets.create_asset(Asset(id=None, name=res[0].filename, type=AssetType.IMAGE, data=res[0].data, + await dao.assets.create_asset(Asset(id=None, name=res[0].filename, type=AssetType.GENERATED, content_type=AssetContentType.IMAGE, data=res[0].data, tg_doc_file_id=doc.document.file_id, tg_photo_file_id=None, linked_char_id=None)) @@ -257,7 +257,7 @@ async def handle_album( if generated_files: for file in generated_files: doc = await message.answer_document(file, caption="✨ Generated result") - await dao.assets.create_asset(Asset(id=None, name=file.filename, type=AssetType.IMAGE, data=file.data, + await dao.assets.create_asset(Asset(id=None, name=file.filename, type=AssetType.GENERATED, content_type=AssetContentType.IMAGE, data=file.data, tg_doc_file_id = doc.document.file_id, tg_photo_file_id = None, linked_char_id = data["char_id"])) else: @@ -312,7 +312,7 @@ async def gen_mode_start( if generated_files: for file in generated_files: doc = await message.answer_document(file, caption="✨ Generated result") - await dao.assets.create_asset(Asset(id=None, name=file.filename, type=AssetType.IMAGE, data=file.data, + await dao.assets.create_asset(Asset(id=None, name=file.filename, type=AssetType.GENERATED, content_type=AssetContentType.IMAGE, data=file.data, tg_doc_file_id=doc.document.file_id, tg_photo_file_id=None, linked_char_id=data["char_id"]))