feat: Separate asset origin type from content type for improved asset categorization and handling.
This commit is contained in:
2
.env
2
.env
@@ -3,7 +3,7 @@ BOT_TOKEN=8495170789:AAHyjjhHwwVtd9_ROnjHqPHRdnmyVr1aeaY
|
|||||||
GEMINI_API_KEY=AIzaSyAHzDYhgjOqZZnvOnOFRGaSkKu4OAN3kZE
|
GEMINI_API_KEY=AIzaSyAHzDYhgjOqZZnvOnOFRGaSkKu4OAN3kZE
|
||||||
MONGO_HOST=mongodb://admin:super_secure_password@31.59.58.220:27017/
|
MONGO_HOST=mongodb://admin:super_secure_password@31.59.58.220:27017/
|
||||||
ADMIN_ID=567047
|
ADMIN_ID=567047
|
||||||
MINIO_ENDPOINT=https://minio.luminic.space
|
MINIO_ENDPOINT=http://31.59.58.220:9000
|
||||||
MINIO_ACCESS_KEY=admin
|
MINIO_ACCESS_KEY=admin
|
||||||
MINIO_SECRET_KEY=SuperSecretPassword123!
|
MINIO_SECRET_KEY=SuperSecretPassword123!
|
||||||
MINIO_BUCKET=ai-char
|
MINIO_BUCKET=ai-char
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ from starlette.requests import Request
|
|||||||
from starlette.responses import Response, JSONResponse
|
from starlette.responses import Response, JSONResponse
|
||||||
|
|
||||||
from api.models.AssetDTO import AssetsResponse, AssetResponse
|
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 repos.dao import DAO
|
||||||
from api.dependency import get_dao
|
from api.dependency import get_dao
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -105,7 +105,8 @@ async def upload_asset(
|
|||||||
|
|
||||||
asset = Asset(
|
asset = Asset(
|
||||||
name=file.filename or "upload",
|
name=file.filename or "upload",
|
||||||
type=AssetType.IMAGE,
|
type=AssetType.UPLOADED,
|
||||||
|
content_type=AssetContentType.IMAGE,
|
||||||
linked_char_id=linked_char_id,
|
linked_char_id=linked_char_id,
|
||||||
data=data,
|
data=data,
|
||||||
thumbnail=thumbnail_bytes
|
thumbnail=thumbnail_bytes
|
||||||
@@ -119,6 +120,7 @@ async def upload_asset(
|
|||||||
id=asset.id,
|
id=asset.id,
|
||||||
name=asset.name,
|
name=asset.name,
|
||||||
type=asset.type.value if hasattr(asset.type, "value") else asset.type,
|
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,
|
linked_char_id=asset.linked_char_id,
|
||||||
created_at=asset.created_at,
|
created_at=asset.created_at,
|
||||||
url=asset.url
|
url=asset.url
|
||||||
@@ -145,7 +147,7 @@ async def regenerate_thumbnails(dao: DAO = Depends(get_dao)):
|
|||||||
updated = 0
|
updated = 0
|
||||||
|
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
if asset.type == AssetType.IMAGE and asset.data :
|
if asset.content_type == AssetContentType.IMAGE and asset.data :
|
||||||
try:
|
try:
|
||||||
thumb = await asyncio.to_thread(create_thumbnail, asset.data)
|
thumb = await asyncio.to_thread(create_thumbnail, asset.data)
|
||||||
if thumb:
|
if thumb:
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from models.Asset import Asset
|
|||||||
class AssetResponse(BaseModel):
|
class AssetResponse(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
type: str
|
type: str # uploaded / generated
|
||||||
|
content_type: str # image / prompt
|
||||||
linked_char_id: Optional[str] = None
|
linked_char_id: Optional[str] = None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
url: Optional[str] = None
|
url: Optional[str] = None
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -11,7 +11,7 @@ from adapters.Exception import GoogleGenerationException
|
|||||||
from adapters.google_adapter import GoogleAdapter
|
from adapters.google_adapter import GoogleAdapter
|
||||||
from api.models.GenerationRequest import GenerationRequest, GenerationResponse
|
from api.models.GenerationRequest import GenerationRequest, GenerationResponse
|
||||||
# Импортируйте ваши модели DAO, Asset, Generation корректно
|
# Импортируйте ваши модели DAO, Asset, Generation корректно
|
||||||
from models.Asset import Asset, AssetType
|
from models.Asset import Asset, AssetType, AssetContentType
|
||||||
from models.Generation import Generation, GenerationStatus
|
from models.Generation import Generation, GenerationStatus
|
||||||
from models.enums import AspectRatios, Quality, GenType
|
from models.enums import AspectRatios, Quality, GenType
|
||||||
from repos.dao import DAO
|
from repos.dao import DAO
|
||||||
@@ -174,7 +174,7 @@ class GenerationService:
|
|||||||
|
|
||||||
# Извлекаем данные (bytes) из ассетов для отправки в Gemini
|
# Извлекаем данные (bytes) из ассетов для отправки в Gemini
|
||||||
for asset in reference_assets:
|
for asset in reference_assets:
|
||||||
if asset.type != AssetType.IMAGE:
|
if asset.content_type != AssetContentType.IMAGE:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
img_data = None
|
img_data = None
|
||||||
@@ -249,7 +249,8 @@ class GenerationService:
|
|||||||
|
|
||||||
new_asset = Asset(
|
new_asset = Asset(
|
||||||
name=f"Generated_{generation.linked_character_id}",
|
name=f"Generated_{generation.linked_character_id}",
|
||||||
type=AssetType.IMAGE,
|
type=AssetType.GENERATED,
|
||||||
|
content_type=AssetContentType.IMAGE,
|
||||||
linked_char_id=generation.linked_character_id,
|
linked_char_id=generation.linked_character_id,
|
||||||
data=None, # Not storing bytes in DB anymore
|
data=None, # Not storing bytes in DB anymore
|
||||||
minio_object_name=filename,
|
minio_object_name=filename,
|
||||||
|
|||||||
@@ -2,18 +2,23 @@ from datetime import datetime, UTC
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Any, List
|
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'
|
IMAGE = 'image'
|
||||||
PROMPT = 'prompt'
|
PROMPT = 'prompt'
|
||||||
|
|
||||||
|
class AssetType(str, Enum):
|
||||||
|
UPLOADED = 'uploaded'
|
||||||
|
GENERATED = 'generated'
|
||||||
|
|
||||||
|
|
||||||
class Asset(BaseModel):
|
class Asset(BaseModel):
|
||||||
id: Optional[str] = None
|
id: Optional[str] = None
|
||||||
name: str
|
name: str
|
||||||
type: AssetType
|
type: AssetType = AssetType.GENERATED
|
||||||
|
content_type: AssetContentType = AssetContentType.IMAGE
|
||||||
linked_char_id: Optional[str] = None
|
linked_char_id: Optional[str] = None
|
||||||
data: Optional[bytes] = None
|
data: Optional[bytes] = None
|
||||||
tg_doc_file_id: Optional[str] = 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))
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||||
updated_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 ---
|
# --- CALCULATED FIELD ---
|
||||||
@computed_field
|
@computed_field
|
||||||
def url(self) -> str:
|
def url(self) -> str:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,7 +8,7 @@ from aiogram.fsm.state import State, StatesGroup
|
|||||||
from aiogram.types import *
|
from aiogram.types import *
|
||||||
from aiogram import Router, F, Bot
|
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 models.Character import Character
|
||||||
from repos.dao import DAO
|
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_bytes = await bot.download_file(file_info.file_path)
|
||||||
file_io = file_bytes.read()
|
file_io = file_bytes.read()
|
||||||
avatar_asset = await dao.assets.create_asset(
|
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))
|
tg_doc_file_id=file_id))
|
||||||
char.avatar_image = avatar_asset.link
|
char.avatar_image = avatar_asset.link
|
||||||
# Отправляем подтверждение
|
# Отправляем подтверждение
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from aiogram.types import *
|
|||||||
|
|
||||||
import keyboards
|
import keyboards
|
||||||
from adapters.google_adapter import GoogleAdapter
|
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.Character import Character
|
||||||
from models.enums import AspectRatios, Quality, GenType
|
from models.enums import AspectRatios, Quality, GenType
|
||||||
from repos.dao import DAO
|
from repos.dao import DAO
|
||||||
@@ -50,7 +50,7 @@ async def generate_image_cmd(message: Message, state: FSMContext, dao: DAO, gemi
|
|||||||
gemini=gemini)
|
gemini=gemini)
|
||||||
await wait_msg.delete()
|
await wait_msg.delete()
|
||||||
doc = await message.answer_document(res[0], caption="Generated result 💫")
|
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))
|
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:
|
if generated_files:
|
||||||
for file in generated_files:
|
for file in generated_files:
|
||||||
doc = await message.answer_document(file, caption="✨ Generated result")
|
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,
|
tg_doc_file_id = doc.document.file_id, tg_photo_file_id = None,
|
||||||
linked_char_id = data["char_id"]))
|
linked_char_id = data["char_id"]))
|
||||||
else:
|
else:
|
||||||
@@ -312,7 +312,7 @@ async def gen_mode_start(
|
|||||||
if generated_files:
|
if generated_files:
|
||||||
for file in generated_files:
|
for file in generated_files:
|
||||||
doc = await message.answer_document(file, caption="✨ Generated result")
|
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,
|
tg_doc_file_id=doc.document.file_id, tg_photo_file_id=None,
|
||||||
linked_char_id=data["char_id"]))
|
linked_char_id=data["char_id"]))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user