feat: Add logging to API endpoints, update generation response model, and refine project configurations.
This commit is contained in:
3
.env
3
.env
@@ -1,4 +1,5 @@
|
|||||||
BOT_TOKEN=8495170789:AAHyjjhHwwVtd9_ROnjHqPHRdnmyVr1aeaY
|
#BOT_TOKEN=8495170789:AAHyjjhHwwVtd9_ROnjHqPHRdnmyVr1aeaY
|
||||||
|
BOT_TOKEN=8011562605:AAF3kyzrZJgii0Jx-H8Sum5Njbo0BdbsiAo
|
||||||
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
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
10
.idea/ai-char-bot.iml
generated
Normal file
10
.idea/ai-char-bot.iml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.13 (ai-char-bot)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
16
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
16
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyAssertTypeInspection" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
||||||
|
<inspection_tool class="PyAsyncCallInspection" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
||||||
|
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredErrors">
|
||||||
|
<list>
|
||||||
|
<option value="N802" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PyTypeCheckerInspection" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
||||||
|
<inspection_tool class="PyUnreachableCodeInspection" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.13 (ai-char-bot)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (ai-char-bot)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ai-char-bot.iml" filepath="$PROJECT_DIR$/.idea/ai-char-bot.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.vscode/launch.json
vendored
Normal file
5
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{"name":"Python Debugger: FastAPI","type":"debugpy","request":"launch","module":"uvicorn","args":["main:app","--reload", "--port", "8090"],"jinja":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
__pycache__/keyboards.cpython-313.pyc
Normal file
BIN
__pycache__/keyboards.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/main.cpython-313.pyc
Normal file
BIN
__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
@@ -1,3 +1,4 @@
|
|||||||
class GoogleGenerationException(Exception):
|
class GoogleGenerationException(Exception):
|
||||||
message: str
|
def __init__(self, message: str):
|
||||||
pass
|
self.message = message
|
||||||
|
super().__init__(message)
|
||||||
BIN
adapters/__pycache__/Exception.cpython-313.pyc
Normal file
BIN
adapters/__pycache__/Exception.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
adapters/__pycache__/kling_adapter.cpython-313.pyc
Normal file
BIN
adapters/__pycache__/kling_adapter.cpython-313.pyc
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Union
|
from typing import List, Union, Tuple, Dict, Any
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from google import genai
|
from google import genai
|
||||||
@@ -27,6 +27,7 @@ class GoogleAdapter:
|
|||||||
"""Вспомогательный метод для подготовки контента (текст + картинки)"""
|
"""Вспомогательный метод для подготовки контента (текст + картинки)"""
|
||||||
contents = [prompt]
|
contents = [prompt]
|
||||||
if images_list:
|
if images_list:
|
||||||
|
logger.info(f"Preparing content with {len(images_list)} images")
|
||||||
for img_bytes in images_list:
|
for img_bytes in images_list:
|
||||||
try:
|
try:
|
||||||
# Gemini API требует PIL Image на входе
|
# Gemini API требует PIL Image на входе
|
||||||
@@ -34,6 +35,8 @@ class GoogleAdapter:
|
|||||||
contents.append(image)
|
contents.append(image)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing input image: {e}")
|
logger.error(f"Error processing input image: {e}")
|
||||||
|
else:
|
||||||
|
logger.info("Preparing content with no images")
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
def generate_text(self, prompt: str, images_list: List[bytes] = None) -> str:
|
def generate_text(self, prompt: str, images_list: List[bytes] = None) -> str:
|
||||||
@@ -59,20 +62,25 @@ class GoogleAdapter:
|
|||||||
for part in response.parts:
|
for part in response.parts:
|
||||||
if part.text:
|
if part.text:
|
||||||
result_text += part.text
|
result_text += part.text
|
||||||
logger.error(f"Generated text: {result_text}")
|
logger.info(f"Generated text length: {len(result_text)}")
|
||||||
return result_text
|
return result_text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Gemini Text API Error: {e}")
|
logger.error(f"Gemini Text API Error: {e}")
|
||||||
return f"Ошибка генерации текста: {e}"
|
raise GoogleGenerationException(f"Gemini Text API Error: {e}")
|
||||||
|
|
||||||
def generate_image(self, prompt: str, aspect_ratio: AspectRatios, quality: Quality, images_list: List[bytes] = None, ) -> List[io.BytesIO]:
|
def generate_image(self, prompt: str, aspect_ratio: AspectRatios, quality: Quality, images_list: List[bytes] = None, ) -> Tuple[List[io.BytesIO], Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Генерация изображений (Text-to-Image или Image-to-Image).
|
Генерация изображений (Text-to-Image или Image-to-Image).
|
||||||
Возвращает список байтовых потоков (готовых к отправке).
|
Возвращает список байтовых потоков (готовых к отправке).
|
||||||
"""
|
"""
|
||||||
contents = self._prepare_contents(prompt, images_list)
|
|
||||||
|
|
||||||
|
contents = self._prepare_contents(prompt, images_list)
|
||||||
|
logger.info(f"Generating image. Prompt length: {len(prompt)}, Ratio: {aspect_ratio}, Quality: {quality}")
|
||||||
|
|
||||||
|
start_time = datetime.now()
|
||||||
|
token_usage = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.client.models.generate_content(
|
response = self.client.models.generate_content(
|
||||||
model=self.IMAGE_MODEL,
|
model=self.IMAGE_MODEL,
|
||||||
@@ -86,6 +94,13 @@ class GoogleAdapter:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
end_time = datetime.now()
|
||||||
|
api_duration = (end_time - start_time).total_seconds()
|
||||||
|
|
||||||
|
if response.usage_metadata:
|
||||||
|
token_usage = response.usage_metadata.total_token_count
|
||||||
|
|
||||||
if response.parts is None and response.candidates[0].finish_reason is not None:
|
if response.parts is None and response.candidates[0].finish_reason is not None:
|
||||||
raise GoogleGenerationException(f"Generation blocked in cause of {response.candidates[0].finish_reason.value}")
|
raise GoogleGenerationException(f"Generation blocked in cause of {response.candidates[0].finish_reason.value}")
|
||||||
|
|
||||||
@@ -111,9 +126,17 @@ class GoogleAdapter:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing output image: {e}")
|
logger.error(f"Error processing output image: {e}")
|
||||||
|
|
||||||
return generated_images
|
if generated_images:
|
||||||
|
logger.info(f"Successfully generated {len(generated_images)} images in {api_duration:.2f}s. Tokens: {token_usage}")
|
||||||
|
else:
|
||||||
|
logger.warning("No images text generated from parts")
|
||||||
|
|
||||||
|
metrics = {
|
||||||
|
"api_execution_time_seconds": api_duration,
|
||||||
|
"token_usage": token_usage
|
||||||
|
}
|
||||||
|
return generated_images, metrics
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Gemini Image API Error: {e}")
|
logger.error(f"Gemini Image API Error: {e}")
|
||||||
# В случае ошибки возвращаем пустой список (или можно рейзить исключение)
|
raise GoogleGenerationException(f"Gemini Image API Error: {e}")
|
||||||
return []
|
|
||||||
BIN
api/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
api/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/__pycache__/dependency.cpython-313.pyc
Normal file
BIN
api/__pycache__/dependency.cpython-313.pyc
Normal file
Binary file not shown.
@@ -25,6 +25,6 @@ def get_dao(mongo_client: AsyncIOMotorClient = Depends(get_mongo_client)) -> DAO
|
|||||||
# Провайдер сервиса (собирается из DAO и Gemini)
|
# Провайдер сервиса (собирается из DAO и Gemini)
|
||||||
def get_generation_service(
|
def get_generation_service(
|
||||||
dao: DAO = Depends(get_dao),
|
dao: DAO = Depends(get_dao),
|
||||||
gemini: GoogleAdapter = Depends(get_gemini_client)
|
gemini: GoogleAdapter = Depends(get_gemini_client),
|
||||||
) -> GenerationService:
|
) -> GenerationService:
|
||||||
return GenerationService(dao, gemini)
|
return GenerationService(dao, gemini)
|
||||||
BIN
api/endpoints/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
api/endpoints/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/endpoints/__pycache__/assets_router.cpython-313.pyc
Normal file
BIN
api/endpoints/__pycache__/assets_router.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/endpoints/__pycache__/character_router.cpython-313.pyc
Normal file
BIN
api/endpoints/__pycache__/character_router.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/endpoints/__pycache__/generation_router.cpython-313.pyc
Normal file
BIN
api/endpoints/__pycache__/generation_router.cpython-313.pyc
Normal file
Binary file not shown.
@@ -13,12 +13,16 @@ from models.Asset import Asset, AssetType
|
|||||||
from repos.dao import DAO
|
from repos.dao import DAO
|
||||||
from api.dependency import get_dao
|
from api.dependency import get_dao
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/assets", tags=["Assets"])
|
router = APIRouter(prefix="/api/assets", tags=["Assets"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{asset_id}")
|
@router.get("/{asset_id}")
|
||||||
async def get_asset(asset_id: str, request: Request,dao: DAO = Depends(get_dao),) -> Response:
|
async def get_asset(asset_id: str, request: Request,dao: DAO = Depends(get_dao),) -> Response:
|
||||||
|
logger.debug(f"get_asset called for ID: {asset_id}")
|
||||||
asset = await dao.assets.get_asset(asset_id)
|
asset = await dao.assets.get_asset(asset_id)
|
||||||
# 2. Проверка на существование
|
# 2. Проверка на существование
|
||||||
if not asset:
|
if not asset:
|
||||||
@@ -32,8 +36,9 @@ async def get_asset(asset_id: str, request: Request,dao: DAO = Depends(get_dao),
|
|||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
async def get_assets(request: Request, dao: DAO = Depends(get_dao), limit: int = 10, offset: int = 0) -> AssetsResponse:
|
async def get_assets(request: Request, dao: DAO = Depends(get_dao), limit: int = 10, offset: int = 0) -> AssetsResponse:
|
||||||
|
logger.info(f"get_assets called. Limit: {limit}, Offset: {offset}")
|
||||||
assets = await dao.assets.get_assets(limit, offset)
|
assets = await dao.assets.get_assets(limit, offset)
|
||||||
assets = await dao.assets.get_assets()
|
# assets = await dao.assets.get_assets() # This line seemed redundant/conflicting in original code
|
||||||
total_count = await dao.assets.get_asset_count()
|
total_count = await dao.assets.get_asset_count()
|
||||||
|
|
||||||
return AssetsResponse(assets=assets, total_count=total_count)
|
return AssetsResponse(assets=assets, total_count=total_count)
|
||||||
@@ -46,6 +51,7 @@ async def upload_asset(
|
|||||||
linked_char_id: Optional[str] = Form(None),
|
linked_char_id: Optional[str] = Form(None),
|
||||||
dao: DAO = Depends(get_dao),
|
dao: DAO = Depends(get_dao),
|
||||||
):
|
):
|
||||||
|
logger.info(f"upload_asset called. Filename: {file.filename}, ContentType: {file.content_type}, LinkedCharId: {linked_char_id}")
|
||||||
if not file.content_type:
|
if not file.content_type:
|
||||||
raise HTTPException(status_code=400, detail="Unknown file type")
|
raise HTTPException(status_code=400, detail="Unknown file type")
|
||||||
|
|
||||||
@@ -65,6 +71,7 @@ async def upload_asset(
|
|||||||
|
|
||||||
asset_id = await dao.assets.create_asset(asset)
|
asset_id = await dao.assets.create_asset(asset)
|
||||||
asset.id = str(asset_id)
|
asset.id = str(asset_id)
|
||||||
|
logger.info(f"Asset created successfully. ID: {asset_id}")
|
||||||
|
|
||||||
return AssetResponse(
|
return AssetResponse(
|
||||||
id=asset.id,
|
id=asset.id,
|
||||||
|
|||||||
@@ -12,11 +12,16 @@ from models.Character import Character
|
|||||||
from repos.dao import DAO
|
from repos.dao import DAO
|
||||||
from api.dependency import get_dao
|
from api.dependency import get_dao
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/characters", tags=["Characters"])
|
router = APIRouter(prefix="/api/characters", tags=["Characters"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[Character])
|
@router.get("/", response_model=List[Character])
|
||||||
async def get_characters(request: Request, dao: DAO = Depends(get_dao), ) -> List[Character]:
|
async def get_characters(request: Request, dao: DAO = Depends(get_dao), ) -> List[Character]:
|
||||||
|
logger.info("get_characters called")
|
||||||
characters = await dao.chars.get_all_characters()
|
characters = await dao.chars.get_all_characters()
|
||||||
return characters
|
return characters
|
||||||
|
|
||||||
@@ -24,6 +29,7 @@ async def get_characters(request: Request, dao: DAO = Depends(get_dao), ) -> Lis
|
|||||||
@router.get("/{character_id}/assets", response_model=AssetsResponse)
|
@router.get("/{character_id}/assets", response_model=AssetsResponse)
|
||||||
async def get_character_assets(character_id: str, dao: DAO = Depends(get_dao), limit: int = 10,
|
async def get_character_assets(character_id: str, dao: DAO = Depends(get_dao), limit: int = 10,
|
||||||
offset: int = 0, ) -> AssetsResponse:
|
offset: int = 0, ) -> AssetsResponse:
|
||||||
|
logger.info(f"get_character_assets called. CharacterID: {character_id}, Limit: {limit}, Offset: {offset}")
|
||||||
character = await dao.chars.get_character(character_id)
|
character = await dao.chars.get_character(character_id)
|
||||||
if character is None:
|
if character is None:
|
||||||
raise HTTPException(status_code=404, detail="Character not found")
|
raise HTTPException(status_code=404, detail="Character not found")
|
||||||
@@ -34,11 +40,13 @@ async def get_character_assets(character_id: str, dao: DAO = Depends(get_dao), l
|
|||||||
|
|
||||||
@router.get("/{character_id}", response_model=Character)
|
@router.get("/{character_id}", response_model=Character)
|
||||||
async def get_character_by_id(character_id: str, request: Request, dao: DAO = Depends(get_dao)) -> Character:
|
async def get_character_by_id(character_id: str, request: Request, dao: DAO = Depends(get_dao)) -> Character:
|
||||||
|
logger.debug(f"get_character_by_id called. ID: {character_id}")
|
||||||
character = await dao.chars.get_character(character_id)
|
character = await dao.chars.get_character(character_id)
|
||||||
return character
|
return character
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{character_id}/_run", response_model=Asset)
|
@router.post("/{character_id}/_run", response_model=GenerationResponse)
|
||||||
async def post_character_generation(character_id: str, generation: GenerationRequest,
|
async def post_character_generation(character_id: str, generation: GenerationRequest,
|
||||||
request: Request) -> GenerationResponse:
|
request: Request) -> GenerationResponse:
|
||||||
|
logger.info(f"post_character_generation called. CharacterID: {character_id}")
|
||||||
generation_service = request.app.state.generation_service
|
generation_service = request.app.state.generation_service
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, UploadFile, File, Form
|
||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
||||||
@@ -11,6 +11,10 @@ from api.models.GenerationRequest import GenerationResponse, GenerationRequest,
|
|||||||
from api.service.generation_service import GenerationService
|
from api.service.generation_service import GenerationService
|
||||||
from models.Generation import Generation
|
from models.Generation import Generation
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix='/api/generations', tags=["Generation"])
|
router = APIRouter(prefix='/api/generations', tags=["Generation"])
|
||||||
|
|
||||||
|
|
||||||
@@ -18,13 +22,31 @@ router = APIRouter(prefix='/api/generations', tags=["Generation"])
|
|||||||
async def ask_prompt_assistant(prompt_request: PromptRequest, request: Request,
|
async def ask_prompt_assistant(prompt_request: PromptRequest, request: Request,
|
||||||
generation_service: GenerationService = Depends(
|
generation_service: GenerationService = Depends(
|
||||||
get_generation_service)) -> PromptResponse:
|
get_generation_service)) -> PromptResponse:
|
||||||
|
logger.info(f"ask_prompt_assistant called with prompt length: {len(prompt_request.prompt)}. Linked assets: {len(prompt_request.linked_assets) if prompt_request.linked_assets else 0}")
|
||||||
generated_prompt = await generation_service.ask_prompt_assistant(prompt_request.prompt, prompt_request.linked_assets)
|
generated_prompt = await generation_service.ask_prompt_assistant(prompt_request.prompt, prompt_request.linked_assets)
|
||||||
return PromptResponse(prompt=generated_prompt)
|
return PromptResponse(prompt=generated_prompt)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/prompt-from-image", response_model=PromptResponse)
|
||||||
|
async def prompt_from_image(
|
||||||
|
prompt: Optional[str] = Form(None),
|
||||||
|
images: List[UploadFile] = File(...),
|
||||||
|
generation_service: GenerationService = Depends(get_generation_service)
|
||||||
|
) -> PromptResponse:
|
||||||
|
logger.info(f"prompt_from_image called. Images count: {len(images)}. Prompt provided: {bool(prompt)}")
|
||||||
|
images_bytes = []
|
||||||
|
for image in images:
|
||||||
|
content = await image.read()
|
||||||
|
images_bytes.append(content)
|
||||||
|
|
||||||
|
generated_prompt = await generation_service.generate_prompt_from_images(images_bytes, prompt)
|
||||||
|
return PromptResponse(prompt=generated_prompt)
|
||||||
|
|
||||||
|
|
||||||
@router.get("", response_model=List[GenerationResponse])
|
@router.get("", response_model=List[GenerationResponse])
|
||||||
async def get_generations(character_id: Optional[str], limit: int = 10, offset: int = 0,
|
async def get_generations(character_id: Optional[str] = None, limit: int = 10, offset: int = 0,
|
||||||
generation_service: GenerationService = Depends(get_generation_service)):
|
generation_service: GenerationService = Depends(get_generation_service)):
|
||||||
|
logger.info(f"get_generations called. CharacterId: {character_id}, Limit: {limit}, Offset: {offset}")
|
||||||
return await generation_service.get_generations(character_id, limit=limit, offset=offset)
|
return await generation_service.get_generations(character_id, limit=limit, offset=offset)
|
||||||
|
|
||||||
|
|
||||||
@@ -32,12 +54,14 @@ async def get_generations(character_id: Optional[str], limit: int = 10, offset:
|
|||||||
async def post_generation(generation: GenerationRequest, request: Request,
|
async def post_generation(generation: GenerationRequest, request: Request,
|
||||||
generation_service: GenerationService = Depends(
|
generation_service: GenerationService = Depends(
|
||||||
get_generation_service)) -> GenerationResponse:
|
get_generation_service)) -> GenerationResponse:
|
||||||
|
logger.info(f"post_generation (run) called. LinkedCharId: {generation.linked_character_id}, PromptLength: {len(generation.prompt)}")
|
||||||
return await generation_service.create_generation_task(generation)
|
return await generation_service.create_generation_task(generation)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{generation_id}", response_model=GenerationResponse)
|
@router.get("/{generation_id}", response_model=GenerationResponse)
|
||||||
async def get_generation(generation_id: str,
|
async def get_generation(generation_id: str,
|
||||||
generation_service: GenerationService = Depends(get_generation_service)) -> GenerationResponse:
|
generation_service: GenerationService = Depends(get_generation_service)) -> GenerationResponse:
|
||||||
|
logger.debug(f"get_generation called for ID: {generation_id}")
|
||||||
return await generation_service.get_generation(generation_id)
|
return await generation_service.get_generation(generation_id)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
from models.Asset import Asset
|
from models.Asset import Asset
|
||||||
from models.Generation import GenerationStatus
|
from models.Generation import GenerationStatus
|
||||||
from models.enums import AspectRatios, Quality
|
from models.enums import AspectRatios, Quality, GenType
|
||||||
|
|
||||||
|
|
||||||
class GenerationRequest(BaseModel):
|
class GenerationRequest(BaseModel):
|
||||||
@@ -20,12 +20,18 @@ class GenerationResponse(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
status: GenerationStatus
|
status: GenerationStatus
|
||||||
failed_reason: Optional[str] = None
|
failed_reason: Optional[str] = None
|
||||||
|
|
||||||
linked_character_id: Optional[str] = None
|
linked_character_id: Optional[str] = None
|
||||||
aspect_ratio: AspectRatios
|
aspect_ratio: AspectRatios
|
||||||
quality: Quality
|
quality: Quality
|
||||||
prompt: str
|
prompt: str
|
||||||
|
tech_prompt: Optional[str] = None
|
||||||
assets_list: List[str]
|
assets_list: List[str]
|
||||||
result: Optional[str] = None
|
result: Optional[str] = None
|
||||||
|
execution_time_seconds: Optional[float] = None
|
||||||
|
api_execution_time_seconds: Optional[float] = None
|
||||||
|
token_usage: Optional[int] = None
|
||||||
|
progress: int = 0
|
||||||
created_at: datetime = datetime.now(UTC)
|
created_at: datetime = datetime.now(UTC)
|
||||||
updated_at: datetime = datetime.now(UTC)
|
updated_at: datetime = datetime.now(UTC)
|
||||||
|
|
||||||
|
|||||||
BIN
api/models/__pycache__/AssetDTO.cpython-313.pyc
Normal file
BIN
api/models/__pycache__/AssetDTO.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/models/__pycache__/GenerationRequest.cpython-313.pyc
Normal file
BIN
api/models/__pycache__/GenerationRequest.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/models/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
api/models/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/service/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
api/service/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
api/service/__pycache__/generation_service.cpython-313.pyc
Normal file
BIN
api/service/__pycache__/generation_service.cpython-313.pyc
Normal file
Binary file not shown.
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, UTC
|
from datetime import datetime, UTC
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Tuple, Any, Dict
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from adapters.Exception import GoogleGenerationException
|
from adapters.Exception import GoogleGenerationException
|
||||||
@@ -11,7 +11,7 @@ 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
|
||||||
from models.Generation import Generation, GenerationStatus
|
from models.Generation import Generation, GenerationStatus
|
||||||
from models.enums import AspectRatios, Quality
|
from models.enums import AspectRatios, Quality, GenType
|
||||||
from repos.dao import DAO
|
from repos.dao import DAO
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -24,20 +24,24 @@ async def generate_image_task(
|
|||||||
aspect_ratio: AspectRatios,
|
aspect_ratio: AspectRatios,
|
||||||
quality: Quality,
|
quality: Quality,
|
||||||
gemini: GoogleAdapter
|
gemini: GoogleAdapter
|
||||||
) -> List[bytes]:
|
) -> Tuple[List[bytes], Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Обертка для вызова синхронного метода Gemini в отдельном потоке.
|
Обертка для вызова синхронного метода Gemini в отдельном потоке.
|
||||||
Возвращает список байтов сгенерированных изображений.
|
Возвращает список байтов сгенерированных изображений.
|
||||||
"""
|
"""
|
||||||
try :
|
try :
|
||||||
|
logger.info(f"Starting generate_image_task with prompt length: {len(prompt)}")
|
||||||
# Запускаем блокирующую операцию в отдельном потоке, чтобы не тормозить Event Loop
|
# Запускаем блокирующую операцию в отдельном потоке, чтобы не тормозить Event Loop
|
||||||
generated_images_io: List[BytesIO] = await asyncio.to_thread(
|
result = await asyncio.to_thread(
|
||||||
gemini.generate_image,
|
gemini.generate_image,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
images_list=media_group_bytes,
|
images_list=media_group_bytes,
|
||||||
aspect_ratio=aspect_ratio,
|
aspect_ratio=aspect_ratio,
|
||||||
quality=quality,
|
quality=quality,
|
||||||
)
|
)
|
||||||
|
generated_images_io, metrics = result
|
||||||
|
|
||||||
|
logger.info(f"generate_image_task completed, received {len(generated_images_io) if generated_images_io else 0} images")
|
||||||
except GoogleGenerationException as e:
|
except GoogleGenerationException as e:
|
||||||
raise e
|
raise e
|
||||||
images_bytes = []
|
images_bytes = []
|
||||||
@@ -51,13 +55,13 @@ async def generate_image_task(
|
|||||||
# Закрываем поток
|
# Закрываем поток
|
||||||
img_io.close()
|
img_io.close()
|
||||||
|
|
||||||
return images_bytes
|
return images_bytes, metrics
|
||||||
|
|
||||||
|
|
||||||
class GenerationService:
|
class GenerationService:
|
||||||
def __init__(self, dao: DAO, gemini: GoogleAdapter):
|
def __init__(self, dao: DAO, gemini: GoogleAdapter):
|
||||||
self.dao = dao
|
self.dao = dao
|
||||||
self.gemini = gemini
|
self.gemini = gemini
|
||||||
|
|
||||||
|
|
||||||
async def ask_prompt_assistant(self, prompt: str, assets: List[str] = None) -> str:
|
async def ask_prompt_assistant(self, prompt: str, assets: List[str] = None) -> str:
|
||||||
future_prompt = """You are an prompt-assistant. You improving user-entered prompts for image generation. User may upload reference image too.
|
future_prompt = """You are an prompt-assistant. You improving user-entered prompts for image generation. User may upload reference image too.
|
||||||
@@ -68,11 +72,20 @@ class GenerationService:
|
|||||||
if assets is not None:
|
if assets is not None:
|
||||||
assets_db = await self.dao.assets.get_assets_by_ids(assets)
|
assets_db = await self.dao.assets.get_assets_by_ids(assets)
|
||||||
assets_data.extend(asset.data for asset in assets_db)
|
assets_data.extend(asset.data for asset in assets_db)
|
||||||
generated_prompt = self.gemini.generate_text(future_prompt, assets_data)
|
generated_prompt = await asyncio.to_thread(self.gemini.generate_text, future_prompt, assets_data)
|
||||||
logger.info(future_prompt)
|
logger.info(future_prompt)
|
||||||
logger.info(generated_prompt)
|
logger.info(generated_prompt)
|
||||||
return generated_prompt
|
return generated_prompt
|
||||||
|
|
||||||
|
async def generate_prompt_from_images(self, images: List[bytes], user_prompt: Optional[str] = None) -> str:
|
||||||
|
technical_prompt = "You are a prompt engineer. Describe this image in detail to create a stable diffusion using this image as reference. "
|
||||||
|
if user_prompt:
|
||||||
|
technical_prompt += f"User also provided this context: {user_prompt}. "
|
||||||
|
|
||||||
|
technical_prompt += "Provide ONLY the detailed prompt."
|
||||||
|
|
||||||
|
return await asyncio.to_thread(self.gemini.generate_text, prompt=technical_prompt, images_list=images)
|
||||||
|
|
||||||
async def get_generations(self, character_id: Optional[str] = None, limit: int = 10, offset: int = 0) -> List[
|
async def get_generations(self, character_id: Optional[str] = None, limit: int = 10, offset: int = 0) -> List[
|
||||||
Generation]:
|
Generation]:
|
||||||
return await self.dao.generations.get_generations(limit=limit, offset=offset)
|
return await self.dao.generations.get_generations(limit=limit, offset=offset)
|
||||||
@@ -97,8 +110,10 @@ class GenerationService:
|
|||||||
generation_model.id = gen_id
|
generation_model.id = gen_id
|
||||||
|
|
||||||
async def runner(gen):
|
async def runner(gen):
|
||||||
|
logger.info(f"Starting background generation task for ID: {gen.id}")
|
||||||
try:
|
try:
|
||||||
await self.create_generation(gen)
|
await self.create_generation(gen)
|
||||||
|
logger.info(f"Background generation task finished for ID: {gen.id}")
|
||||||
except Exception:
|
except Exception:
|
||||||
# если генерация уже пошла и упала — пометим FAILED
|
# если генерация уже пошла и упала — пометим FAILED
|
||||||
try:
|
try:
|
||||||
@@ -125,6 +140,8 @@ class GenerationService:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
async def create_generation(self, generation: Generation):
|
async def create_generation(self, generation: Generation):
|
||||||
|
start_time = datetime.now()
|
||||||
|
logger.info(f"Processing generation {generation.id}. Character ID: {generation.linked_character_id}")
|
||||||
|
|
||||||
# 2. Получаем ассеты-референсы (если они есть)
|
# 2. Получаем ассеты-референсы (если они есть)
|
||||||
reference_assets: List[Asset] = []
|
reference_assets: List[Asset] = []
|
||||||
@@ -146,27 +163,47 @@ class GenerationService:
|
|||||||
if asset.data is not None and asset.type == AssetType.IMAGE
|
if asset.data is not None and asset.type == AssetType.IMAGE
|
||||||
)
|
)
|
||||||
generation_prompt+=f"PROMPT: {generation.prompt}"
|
generation_prompt+=f"PROMPT: {generation.prompt}"
|
||||||
|
logger.info(f"Final generation prompt assembled. Length: {len(generation_prompt)}. Media count: {len(media_group_bytes)}")
|
||||||
|
|
||||||
# 3. Запускаем процесс генерации
|
# 3. Запускаем процесс генерации и симуляцию прогресса
|
||||||
|
progress_task = asyncio.create_task(self._simulate_progress(generation))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
generated_bytes_list = await generate_image_task(
|
|
||||||
|
# Default to Image Generation (Gemini)
|
||||||
|
generated_bytes_list, metrics = await generate_image_task(
|
||||||
prompt=generation_prompt, # или request.prompt
|
prompt=generation_prompt, # или request.prompt
|
||||||
media_group_bytes=media_group_bytes,
|
media_group_bytes=media_group_bytes,
|
||||||
aspect_ratio=generation.aspect_ratio, # предполагаем поля в request
|
aspect_ratio=generation.aspect_ratio, # предполагаем поля в request
|
||||||
quality=generation.quality,
|
quality=generation.quality,
|
||||||
gemini=self.gemini
|
gemini=self.gemini
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update metrics from API (Common for both)
|
||||||
|
generation.api_execution_time_seconds = metrics.get("api_execution_time_seconds")
|
||||||
|
generation.token_usage = metrics.get("token_usage")
|
||||||
|
|
||||||
except GoogleGenerationException as e:
|
except GoogleGenerationException as e:
|
||||||
generation.status = GenerationStatus.FAILED
|
generation.status = GenerationStatus.FAILED
|
||||||
generation.failed_reason = str(e.message)
|
generation.failed_reason = str(e)
|
||||||
generation.updated_at = datetime.now(UTC)
|
generation.updated_at = datetime.now(UTC)
|
||||||
await self.dao.generations.update_generation(generation)
|
await self.dao.generations.update_generation(generation)
|
||||||
raise
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Тут стоит добавить логирование ошибки
|
# Тут стоит добавить логирование ошибки
|
||||||
logging.error(f"Generation failed: {e}")
|
logging.error(f"Generation failed: {e}")
|
||||||
# Можно обновить статус генерации на FAILED в БД
|
generation.status = GenerationStatus.FAILED
|
||||||
|
generation.failed_reason = str(e)
|
||||||
|
generation.updated_at = datetime.now(UTC)
|
||||||
|
await self.dao.generations.update_generation(generation)
|
||||||
raise e
|
raise e
|
||||||
|
finally:
|
||||||
|
if not progress_task.done():
|
||||||
|
progress_task.cancel()
|
||||||
|
try:
|
||||||
|
await progress_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
# 4. Сохраняем полученные изображения как новые Ассеты
|
# 4. Сохраняем полученные изображения как новые Ассеты
|
||||||
created_assets: List[Asset] = []
|
created_assets: List[Asset] = []
|
||||||
@@ -192,6 +229,37 @@ class GenerationService:
|
|||||||
|
|
||||||
generation.assets_list = result_ids
|
generation.assets_list = result_ids
|
||||||
generation.status = GenerationStatus.DONE
|
generation.status = GenerationStatus.DONE
|
||||||
|
generation.progress = 100
|
||||||
generation.updated_at = datetime.now(UTC)
|
generation.updated_at = datetime.now(UTC)
|
||||||
generation.tech_prompt = generation_prompt
|
generation.tech_prompt = generation_prompt
|
||||||
|
|
||||||
|
end_time = datetime.now()
|
||||||
|
generation.execution_time_seconds = (end_time - start_time).total_seconds()
|
||||||
|
|
||||||
await self.dao.generations.update_generation(generation)
|
await self.dao.generations.update_generation(generation)
|
||||||
|
logger.info(f"Generation {generation.id} completed successfully. {len(created_assets)} assets created. Total Time: {generation.execution_time_seconds:.2f}s")
|
||||||
|
|
||||||
|
|
||||||
|
async def _simulate_progress(self, generation: Generation):
|
||||||
|
"""
|
||||||
|
Increments progress from 0 to 90 over ~20 seconds.
|
||||||
|
"""
|
||||||
|
current_progress = 0
|
||||||
|
try:
|
||||||
|
while current_progress < 90:
|
||||||
|
await asyncio.sleep(4)
|
||||||
|
# Random increment between 5 and 15
|
||||||
|
increment = random.randint(5, 15)
|
||||||
|
current_progress = min(current_progress + increment, 90)
|
||||||
|
|
||||||
|
# Fetch latest state (optional, but good practice to avoid overwriting unrelated fields)
|
||||||
|
# But for simplicity here we just use the object we have and save it.
|
||||||
|
# Ideally, we should fetch-update-save or use partial update if DAO supports it.
|
||||||
|
# Assuming simple update is fine for now.
|
||||||
|
generation.progress = current_progress
|
||||||
|
await self.dao.generations.update_generation(generation)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# Task cancelled, generation finished (or failed)
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in progress simulation: {e}")
|
||||||
|
|||||||
3
main.py
3
main.py
@@ -43,6 +43,7 @@ load_dotenv()
|
|||||||
# --- КОНФИГУРАЦИЯ ---
|
# --- КОНФИГУРАЦИЯ ---
|
||||||
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
||||||
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
||||||
|
|
||||||
MONGO_HOST = os.getenv("MONGO_HOST") # Например: mongodb://localhost:27017
|
MONGO_HOST = os.getenv("MONGO_HOST") # Например: mongodb://localhost:27017
|
||||||
DB_NAME = os.getenv("DB_NAME", "my_bot_db") # Имя базы данных
|
DB_NAME = os.getenv("DB_NAME", "my_bot_db") # Имя базы данных
|
||||||
ADMIN_ID = int(os.getenv("ADMIN_ID", 0))
|
ADMIN_ID = int(os.getenv("ADMIN_ID", 0))
|
||||||
@@ -63,6 +64,7 @@ mongo_client = AsyncIOMotorClient(MONGO_HOST)
|
|||||||
users_repo = UsersRepo(mongo_client)
|
users_repo = UsersRepo(mongo_client)
|
||||||
char_repo = CharacterRepo(mongo_client)
|
char_repo = CharacterRepo(mongo_client)
|
||||||
dao = DAO(mongo_client) # Главный DAO для бота
|
dao = DAO(mongo_client) # Главный DAO для бота
|
||||||
|
dao = DAO(mongo_client) # Главный DAO для бота
|
||||||
gemini = GoogleAdapter(api_key=GEMINI_API_KEY)
|
gemini = GoogleAdapter(api_key=GEMINI_API_KEY)
|
||||||
generation_service = GenerationService(dao, gemini)
|
generation_service = GenerationService(dao, gemini)
|
||||||
|
|
||||||
@@ -113,6 +115,7 @@ async def lifespan(app: FastAPI):
|
|||||||
# Инициализируем DAO для ассетов и кладем в state приложения
|
# Инициализируем DAO для ассетов и кладем в state приложения
|
||||||
# Теперь в эндпоинтах можно делать request.app.state.assets_dao
|
# Теперь в эндпоинтах можно делать request.app.state.assets_dao
|
||||||
|
|
||||||
|
app.state.mongo_client = mongo_client
|
||||||
app.state.mongo_client = mongo_client
|
app.state.mongo_client = mongo_client
|
||||||
app.state.gemini_client = gemini
|
app.state.gemini_client = gemini
|
||||||
|
|
||||||
|
|||||||
BIN
middlewares/__pycache__/album.cpython-313.pyc
Normal file
BIN
middlewares/__pycache__/album.cpython-313.pyc
Normal file
Binary file not shown.
BIN
middlewares/__pycache__/dao.cpython-313.pyc
Normal file
BIN
middlewares/__pycache__/dao.cpython-313.pyc
Normal file
Binary file not shown.
@@ -5,7 +5,7 @@ from typing import List, Optional
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from models.Asset import Asset
|
from models.Asset import Asset
|
||||||
from models.enums import AspectRatios, Quality
|
from models.enums import AspectRatios, Quality, GenType
|
||||||
|
|
||||||
|
|
||||||
class GenerationStatus(str, Enum):
|
class GenerationStatus(str, Enum):
|
||||||
@@ -24,5 +24,10 @@ class Generation(BaseModel):
|
|||||||
tech_prompt: Optional[str] = None
|
tech_prompt: Optional[str] = None
|
||||||
assets_list: List[str]
|
assets_list: List[str]
|
||||||
result: Optional[str] = None
|
result: Optional[str] = None
|
||||||
|
progress: int = 0
|
||||||
|
execution_time_seconds: Optional[float] = None
|
||||||
|
api_execution_time_seconds: Optional[float] = None
|
||||||
|
token_usage: Optional[int] = None
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
BIN
models/__pycache__/Asset.cpython-313.pyc
Normal file
BIN
models/__pycache__/Asset.cpython-313.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/Character.cpython-313.pyc
Normal file
BIN
models/__pycache__/Character.cpython-313.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/Generation.cpython-313.pyc
Normal file
BIN
models/__pycache__/Generation.cpython-313.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/enums.cpython-313.pyc
Normal file
BIN
models/__pycache__/enums.cpython-313.pyc
Normal file
Binary file not shown.
@@ -39,5 +39,5 @@ class GenType(str, Enum):
|
|||||||
def value_type(self) -> str:
|
def value_type(self) -> str:
|
||||||
return {
|
return {
|
||||||
GenType.TEXT: 'Text',
|
GenType.TEXT: 'Text',
|
||||||
GenType.IMAGE: 'Image'
|
GenType.IMAGE: 'Image',
|
||||||
}[self]
|
}[self]
|
||||||
|
|||||||
BIN
repos/__pycache__/assets_repo.cpython-313.pyc
Normal file
BIN
repos/__pycache__/assets_repo.cpython-313.pyc
Normal file
Binary file not shown.
BIN
repos/__pycache__/char_repo.cpython-313.pyc
Normal file
BIN
repos/__pycache__/char_repo.cpython-313.pyc
Normal file
Binary file not shown.
BIN
repos/__pycache__/dao.cpython-313.pyc
Normal file
BIN
repos/__pycache__/dao.cpython-313.pyc
Normal file
Binary file not shown.
BIN
repos/__pycache__/generation_repo.cpython-313.pyc
Normal file
BIN
repos/__pycache__/generation_repo.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
routers/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
routers/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routers/__pycache__/assets_router.cpython-313.pyc
Normal file
BIN
routers/__pycache__/assets_router.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routers/__pycache__/auth_router.cpython-313.pyc
Normal file
BIN
routers/__pycache__/auth_router.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routers/__pycache__/char_router.cpython-313.pyc
Normal file
BIN
routers/__pycache__/char_router.cpython-313.pyc
Normal file
Binary file not shown.
BIN
routers/__pycache__/gen_router.cpython-313.pyc
Normal file
BIN
routers/__pycache__/gen_router.cpython-313.pyc
Normal file
Binary file not shown.
@@ -236,9 +236,6 @@ async def handle_album(
|
|||||||
for msg in album:
|
for msg in album:
|
||||||
if msg.photo:
|
if msg.photo:
|
||||||
file_ids.append(msg.photo[-1].file_id)
|
file_ids.append(msg.photo[-1].file_id)
|
||||||
elif msg.video:
|
|
||||||
# Если нужно, можно добавить обработку видео (пока пропускаем)
|
|
||||||
pass
|
|
||||||
|
|
||||||
await message.answer(f"📥 Принято {len(album)} файлов. Начинаю генерацию...")
|
await message.answer(f"📥 Принято {len(album)} файлов. Начинаю генерацию...")
|
||||||
wait_msg = await message.answer("🎨 Генерирую...")
|
wait_msg = await message.answer("🎨 Генерирую...")
|
||||||
|
|||||||
Reference in New Issue
Block a user