+ fixes
This commit is contained in:
@@ -6,7 +6,6 @@ from aiogram.types import Message
|
|||||||
|
|
||||||
class AlbumMiddleware(BaseMiddleware):
|
class AlbumMiddleware(BaseMiddleware):
|
||||||
def __init__(self, latency: float = 0.5):
|
def __init__(self, latency: float = 0.5):
|
||||||
# latency - задержка в секундах для сбора частей альбома
|
|
||||||
self.latency = latency
|
self.latency = latency
|
||||||
self.album_data: Dict[str, List[Message]] = {}
|
self.album_data: Dict[str, List[Message]] = {}
|
||||||
|
|
||||||
@@ -16,36 +15,33 @@ class AlbumMiddleware(BaseMiddleware):
|
|||||||
event: Message,
|
event: Message,
|
||||||
data: Dict[str, Any]
|
data: Dict[str, Any]
|
||||||
) -> Any:
|
) -> Any:
|
||||||
# Если у сообщения нет media_group_id, это не альбом -> пропускаем дальше как обычно
|
|
||||||
if not event.media_group_id:
|
if not event.media_group_id:
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
group_id = event.media_group_id
|
group_id = event.media_group_id
|
||||||
|
|
||||||
try:
|
# Если это первое сообщение группы
|
||||||
# Если этот альбом мы еще не видели (первое сообщение из пачки)
|
if group_id not in self.album_data:
|
||||||
if group_id not in self.album_data:
|
self.album_data[group_id] = [event]
|
||||||
self.album_data[group_id] = [event] # Создаем список
|
try:
|
||||||
await asyncio.sleep(self.latency) # Ждем остальные части
|
# Ждем сбора остальных частей
|
||||||
|
await asyncio.sleep(self.latency)
|
||||||
|
|
||||||
# После ожидания кладем собранный список в data
|
# Проверяем, что ключ все еще существует (на всякий случай)
|
||||||
# Теперь в хендлере будет доступен аргумент 'album'
|
if group_id in self.album_data:
|
||||||
data["album"] = self.album_data[group_id]
|
# Передаем собранный альбом в хендлер
|
||||||
|
# Сортируем по message_id, чтобы порядок был верным
|
||||||
|
self.album_data[group_id].sort(key=lambda x: x.message_id)
|
||||||
|
data["album"] = self.album_data[group_id]
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
# Вызываем хендлер ОДИН раз
|
finally:
|
||||||
return await handler(event, data)
|
# ЧИСТКА: Удаляем всегда, если это "головной" поток, который создал запись
|
||||||
|
# Проверяем, что мы удаляем именно то, что создали, и ключ существует
|
||||||
else:
|
if group_id in self.album_data and self.album_data[group_id][0] == event:
|
||||||
# Если альбом уже собирается, просто добавляем сообщение в список
|
|
||||||
# и НЕ вызываем хендлер (прерываем цепочку для этого сообщения)
|
|
||||||
self.album_data[group_id].append(event)
|
|
||||||
return
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Чистим память после обработки, если это был "главный" поток обработки
|
|
||||||
if group_id in self.album_data and len(self.album_data[group_id]) > 1:
|
|
||||||
# Маленький хак: удаляем только если обработчик завершился
|
|
||||||
# Проверка len нужна, чтобы не удалить раньше времени в параллельных тасках,
|
|
||||||
# но корректнее просто удалять в блоке первого сообщения.
|
|
||||||
if event == self.album_data[group_id][0]:
|
|
||||||
del self.album_data[group_id]
|
del self.album_data[group_id]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Если группа уже собирается - просто добавляем и выходим
|
||||||
|
self.album_data[group_id].append(event)
|
||||||
|
return
|
||||||
@@ -2,8 +2,9 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class Character(BaseModel):
|
class Character(BaseModel):
|
||||||
id: str
|
id: str | None
|
||||||
name: str
|
name: str
|
||||||
character_image: bytes
|
character_image_doc_tg_id: str
|
||||||
|
character_image_tg_id: str | None
|
||||||
character_bio: str
|
character_bio: str
|
||||||
|
|
||||||
|
|||||||
@@ -35,3 +35,7 @@ class CharacterRepo:
|
|||||||
characters.append(Character(**doc))
|
characters.append(Character(**doc))
|
||||||
|
|
||||||
return characters
|
return characters
|
||||||
|
|
||||||
|
async def update_char(self, char_id: str, character: Character) -> None:
|
||||||
|
await self.collection.update_one({"_id": ObjectId(char_id)}, {"$set": character.model_dump()})
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
@@ -47,36 +50,43 @@ async def new_char_bio(message: Message, state: FSMContext, dao: DAO, bot: Bot):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# ВОТ ТУТ скачиваем файл (прямо перед сохранением)
|
# ВОТ ТУТ скачиваем файл (прямо перед сохранением)
|
||||||
file_io = await bot.download(file_id)
|
# file_io = await bot.download(file_id)
|
||||||
photo_bytes = file_io.getvalue() # Получаем байты
|
# photo_bytes = file_io.getvalue() # Получаем байты
|
||||||
|
|
||||||
# Создаем модель
|
# Создаем модель
|
||||||
char = Character(
|
char = Character(
|
||||||
id=None,
|
id=None,
|
||||||
name=name,
|
name=name,
|
||||||
character_image=photo_bytes,
|
# character_image=photo_bytes,
|
||||||
|
character_image_tg_id=None,
|
||||||
|
character_image_doc_tg_id=file_id,
|
||||||
character_bio=bio
|
character_bio=bio
|
||||||
)
|
)
|
||||||
|
|
||||||
# Сохраняем через DAO
|
# Сохраняем через DAO
|
||||||
await dao.chars.add_character(char)
|
await dao.chars.add_character(char)
|
||||||
|
file_info = await bot.get_file(char.character_image_doc_tg_id)
|
||||||
|
file_bytes = await bot.download_file(file_info.file_path)
|
||||||
# Отправляем подтверждение
|
# Отправляем подтверждение
|
||||||
# Используем байты для отправки обратно
|
# Используем байты для отправки обратно
|
||||||
await message.answer_photo(
|
photo_msg = await message.answer_photo(
|
||||||
photo=BufferedInputFile(photo_bytes, filename="char.png"),
|
photo=BufferedInputFile(file_bytes.read(),
|
||||||
|
filename="char.jpg") if not char.character_image_tg_id else char.character_image_tg_id,
|
||||||
caption=(
|
caption=(
|
||||||
"🎉 <b>Персонаж создан!</b>\n\n"
|
"🎉 <b>Персонаж создан!</b>\n\n"
|
||||||
f"👤 <b>Имя:</b> {char.name}\n"
|
f"👤 <b>Имя:</b> {char.name}\n"
|
||||||
f"📝 <b>Био:</b> {char.character_bio}"
|
f"📝 <b>Био:</b> {char.character_bio}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
char.character_image_tg_id = photo_msg.photo[0].file_id
|
||||||
|
await dao.chars.update_char(char.id, char)
|
||||||
await wait_msg.delete()
|
await wait_msg.delete()
|
||||||
|
|
||||||
# Сбрасываем состояние
|
# Сбрасываем состояние
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
await wait_msg.edit_text(f"❌ Ошибка при сохранении: {e}")
|
await wait_msg.edit_text(f"❌ Ошибка при сохранении: {e}")
|
||||||
# Не сбрасываем стейт, даем возможность попробовать ввести био снова или начать заново
|
# Не сбрасываем стейт, даем возможность попробовать ввести био снова или начать заново
|
||||||
|
|
||||||
@@ -98,7 +108,7 @@ async def get_chars(message: Message, state: FSMContext, dao: DAO):
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("char_info_"))
|
@router.callback_query(F.data.startswith("char_info_"))
|
||||||
async def get_char_info(callback_query: CallbackQuery, state: FSMContext, dao: DAO):
|
async def get_char_info(callback_query: CallbackQuery, state: FSMContext, dao: DAO, bot: Bot):
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
wait_msg = await callback_query.message.answer("Ищем инфу о персонаже")
|
wait_msg = await callback_query.message.answer("Ищем инфу о персонаже")
|
||||||
char = await dao.chars.get_character(callback_query.data.split("_")[-1])
|
char = await dao.chars.get_character(callback_query.data.split("_")[-1])
|
||||||
@@ -109,17 +119,19 @@ async def get_char_info(callback_query: CallbackQuery, state: FSMContext, dao: D
|
|||||||
return
|
return
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Запросить фото в документе", callback_data=f'char_photo_file_{char.id}')]])
|
[InlineKeyboardButton(text="Запросить фото в документе", callback_data=f'char_photo_file_{char.id}')]])
|
||||||
await callback_query.message.answer_photo(photo=BufferedInputFile(char.character_image, f"photo_{char.id}.png"), caption=f"👤 <b>Имя:</b> {char.name}\n"
|
|
||||||
f"📝 <b>Био:</b> {char.character_bio}",
|
|
||||||
reply_markup=keyboard)
|
|
||||||
|
|
||||||
|
photo_msg = await callback_query.message.answer_photo(
|
||||||
|
photo=char.character_image_tg_id,
|
||||||
|
caption=f"👤 <b>Имя:</b> {char.name}\n"
|
||||||
|
f"📝 <b>Био:</b> {char.character_bio}",
|
||||||
|
reply_markup=keyboard)
|
||||||
await wait_msg.delete()
|
await wait_msg.delete()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("char_photo_file"))
|
@router.callback_query(F.data.startswith("char_photo_file"))
|
||||||
async def get_char_info_photo_file(callback_query: CallbackQuery, state: FSMContext, dao: DAO):
|
async def get_char_info_photo_file(callback_query: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
char = await dao.chars.get_character(callback_query.data.split("_")[-1])
|
char = await dao.chars.get_character(callback_query.data.split("_")[-1])
|
||||||
await callback_query.message.answer_document(BufferedInputFile(char.character_image, f"photo_{char.id}.png"))
|
await callback_query.message.answer_document(char.character_image_doc_tg_id)
|
||||||
|
|
||||||
|
|
||||||
# 4. Хендлер-помощник (если отправили команду без файла)
|
# 4. Хендлер-помощник (если отправили команду без файла)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
import random
|
import random
|
||||||
from enum import Enum
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from aiogram import Router, Bot, F
|
from aiogram import Router, Bot, F
|
||||||
@@ -10,7 +11,6 @@ from aiogram.filters import *
|
|||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import StatesGroup, State
|
from aiogram.fsm.state import StatesGroup, State
|
||||||
from aiogram.types import *
|
from aiogram.types import *
|
||||||
from aiogram.types import message
|
|
||||||
|
|
||||||
import keyboards
|
import keyboards
|
||||||
from adapters.google_adapter import GoogleAdapter
|
from adapters.google_adapter import GoogleAdapter
|
||||||
@@ -26,13 +26,11 @@ class States(StatesGroup):
|
|||||||
gen_mode = State()
|
gen_mode = State()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def init_gen_mode(state: FSMContext, dao: DAO):
|
async def init_gen_mode(state: FSMContext, dao: DAO):
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
data['aspect_ratio'] = AspectRatios.NINESIXTEEN.name
|
data['aspect_ratio'] = AspectRatios.NINESIXTEEN.name
|
||||||
data['quality'] = Quality.ONEK.name
|
data['quality'] = Quality.ONEK.name
|
||||||
data['type'] = GenType.TEXT.name
|
data['type'] = GenType.IMAGE.name
|
||||||
await state.update_data(data)
|
await state.update_data(data)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,6 +61,8 @@ async def gen_mode_on(callback_query: CallbackQuery, state: FSMContext, dao: DAO
|
|||||||
|
|
||||||
@router.callback_query(States.gen_mode_wait_char, F.data.startswith("select_char_"))
|
@router.callback_query(States.gen_mode_wait_char, F.data.startswith("select_char_"))
|
||||||
async def select_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def select_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
|
await call.message.delete()
|
||||||
await state.update_data({"char_id": call.data.split("_")[-1]})
|
await state.update_data({"char_id": call.data.split("_")[-1]})
|
||||||
await init_gen_mode(state=state, dao=dao)
|
await init_gen_mode(state=state, dao=dao)
|
||||||
await state.set_state(States.gen_mode)
|
await state.set_state(States.gen_mode)
|
||||||
@@ -71,6 +71,7 @@ async def select_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
|||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_off')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_off')
|
||||||
async def gen_mode_off(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def gen_mode_off(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
await state.clear()
|
await state.clear()
|
||||||
await state.set_data({})
|
await state.set_data({})
|
||||||
await call.message.delete()
|
await call.message.delete()
|
||||||
@@ -79,6 +80,7 @@ async def gen_mode_off(call: CallbackQuery, state: FSMContext, dao: DAO):
|
|||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_char')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_char')
|
||||||
async def gen_mode_change_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def gen_mode_change_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
chars = await dao.chars.get_all_characters()
|
chars = await dao.chars.get_all_characters()
|
||||||
if len(chars) == 0:
|
if len(chars) == 0:
|
||||||
await call.message.edit_caption(
|
await call.message.edit_caption(
|
||||||
@@ -90,17 +92,20 @@ async def gen_mode_change_char(call: CallbackQuery, state: FSMContext, dao: DAO)
|
|||||||
for char in chars:
|
for char in chars:
|
||||||
keyboards.append(InlineKeyboardButton(text=char.name, callback_data=f'select_new_char_{char.id}'))
|
keyboards.append(InlineKeyboardButton(text=char.name, callback_data=f'select_new_char_{char.id}'))
|
||||||
keyboards.append(InlineKeyboardButton(text="⬅️ Назад", callback_data="gen_mode_cancel_char_change"))
|
keyboards.append(InlineKeyboardButton(text="⬅️ Назад", callback_data="gen_mode_cancel_char_change"))
|
||||||
await call.message.edit_caption("Выбери персонажа", reply_markup=InlineKeyboardMarkup(inline_keyboard=[keyboards]))
|
await call.message.edit_caption("Выбери персонажа",
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=[keyboards]))
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data.startswith('select_new_char_'))
|
@router.callback_query(States.gen_mode, F.data.startswith('select_new_char_'))
|
||||||
async def change_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def change_char(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
await state.update_data({"char_id": call.data.split("_")[-1]})
|
await state.update_data({"char_id": call.data.split("_")[-1]})
|
||||||
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_aspect_ratio')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_aspect_ratio')
|
||||||
async def gen_mode_change_aspect_ratio(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def gen_mode_change_aspect_ratio(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
keyboards = []
|
keyboards = []
|
||||||
for ratio in AspectRatios:
|
for ratio in AspectRatios:
|
||||||
keyboards.append(InlineKeyboardButton(text=ratio.value, callback_data=f'select_ratio_{ratio.name}'))
|
keyboards.append(InlineKeyboardButton(text=ratio.value, callback_data=f'select_ratio_{ratio.name}'))
|
||||||
@@ -111,12 +116,14 @@ async def gen_mode_change_aspect_ratio(call: CallbackQuery, state: FSMContext, d
|
|||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data.startswith('select_ratio_'))
|
@router.callback_query(States.gen_mode, F.data.startswith('select_ratio_'))
|
||||||
async def change_aspect_ratio(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def change_aspect_ratio(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
await state.update_data({"aspect_ratio": call.data.split("_")[-1]})
|
await state.update_data({"aspect_ratio": call.data.split("_")[-1]})
|
||||||
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_quality')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_quality')
|
||||||
async def gen_mode_change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def gen_mode_change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
keyboards = []
|
keyboards = []
|
||||||
for quality in Quality:
|
for quality in Quality:
|
||||||
keyboards.append(InlineKeyboardButton(text=quality.value, callback_data=f'select_quality_{quality.name}'))
|
keyboards.append(InlineKeyboardButton(text=quality.value, callback_data=f'select_quality_{quality.name}'))
|
||||||
@@ -127,12 +134,14 @@ async def gen_mode_change_quality(call: CallbackQuery, state: FSMContext, dao: D
|
|||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data.startswith('select_quality_'))
|
@router.callback_query(States.gen_mode, F.data.startswith('select_quality_'))
|
||||||
async def change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
await state.update_data({"quality": call.data.split("_")[-1]})
|
await state.update_data({"quality": call.data.split("_")[-1]})
|
||||||
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_type')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_change_type')
|
||||||
async def gen_mode_change_type(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def gen_mode_change_type(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
keyboards = []
|
keyboards = []
|
||||||
for gen_type in GenType:
|
for gen_type in GenType:
|
||||||
keyboards.append(InlineKeyboardButton(text=gen_type.value, callback_data=f'select_type_{gen_type.name}'))
|
keyboards.append(InlineKeyboardButton(text=gen_type.value, callback_data=f'select_type_{gen_type.name}'))
|
||||||
@@ -143,6 +152,7 @@ async def gen_mode_change_type(call: CallbackQuery, state: FSMContext, dao: DAO)
|
|||||||
|
|
||||||
@router.callback_query(States.gen_mode, F.data.startswith('select_type_'))
|
@router.callback_query(States.gen_mode, F.data.startswith('select_type_'))
|
||||||
async def change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
await state.update_data({"type": call.data.split("_")[-1]})
|
await state.update_data({"type": call.data.split("_")[-1]})
|
||||||
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
||||||
|
|
||||||
@@ -152,6 +162,7 @@ async def change_quality(call: CallbackQuery, state: FSMContext, dao: DAO):
|
|||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_cancel_type_change')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_cancel_type_change')
|
||||||
@router.callback_query(States.gen_mode, F.data == 'gen_mode_cancel_quality_change')
|
@router.callback_query(States.gen_mode, F.data == 'gen_mode_cancel_quality_change')
|
||||||
async def cancel_gen_mode_change(call: CallbackQuery, state: FSMContext, dao: DAO):
|
async def cancel_gen_mode_change(call: CallbackQuery, state: FSMContext, dao: DAO):
|
||||||
|
await call.answer()
|
||||||
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
await gen_mode_base_msg(call.message, state=state, dao=dao)
|
||||||
|
|
||||||
|
|
||||||
@@ -159,7 +170,7 @@ async def gen_mode_base_msg(message: Message, state: FSMContext, dao: DAO, call_
|
|||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
char: Character = await dao.chars.get_character(data["char_id"])
|
char: Character = await dao.chars.get_character(data["char_id"])
|
||||||
if call_type == "start":
|
if call_type == "start":
|
||||||
await message.answer_photo(BufferedInputFile(char.character_image, f'{char.id}.png'),
|
await message.answer_photo(char.character_image_tg_id,
|
||||||
caption="🎉 Режим генерации включен! Просто пиши мне промпт и я отправлю в генерацию по указанным настройкам.\n\n"
|
caption="🎉 Режим генерации включен! Просто пиши мне промпт и я отправлю в генерацию по указанным настройкам.\n\n"
|
||||||
"<b>Фото девушки грузить не надо, оно загрузится по дефолту</b>\n\n"
|
"<b>Фото девушки грузить не надо, оно загрузится по дефолту</b>\n\n"
|
||||||
"Но дополнительные фото можно загрузить.",
|
"Но дополнительные фото можно загрузить.",
|
||||||
@@ -168,7 +179,7 @@ async def gen_mode_base_msg(message: Message, state: FSMContext, dao: DAO, call_
|
|||||||
try:
|
try:
|
||||||
await message.edit_caption(
|
await message.edit_caption(
|
||||||
caption="🎉 Режим генерации включен!",
|
caption="🎉 Режим генерации включен!",
|
||||||
reply_markup=await keyboards.get_gen_mode_kb(state=state, dao=dao))
|
reply_markup=await keyboards.get_gen_mode_kb(state=state, dao=dao))
|
||||||
except TelegramBadRequest as tbr:
|
except TelegramBadRequest as tbr:
|
||||||
await message.edit_text(
|
await message.edit_text(
|
||||||
text="🎉 Режим генерации включен!",
|
text="🎉 Режим генерации включен!",
|
||||||
@@ -226,7 +237,7 @@ async def handle_album(
|
|||||||
await message.answer_document(file, caption="✨ Generated result")
|
await message.answer_document(file, caption="✨ Generated result")
|
||||||
else:
|
else:
|
||||||
await message.answer("❌ Генерация не вернула изображений.")
|
await message.answer("❌ Генерация не вернула изображений.")
|
||||||
await gen_mode_base_msg(message=message, state=state, dao=dao,call_type="start" )
|
await gen_mode_base_msg(message=message, state=state, dao=dao, call_type="start")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await wait_msg.edit_text(f"❌ Ошибка: {e}")
|
await wait_msg.edit_text(f"❌ Ошибка: {e}")
|
||||||
@@ -260,24 +271,31 @@ async def gen_mode_start(
|
|||||||
|
|
||||||
wait_msg = await message.answer("🎨 Генерирую...")
|
wait_msg = await message.answer("🎨 Генерирую...")
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
try:
|
try:
|
||||||
generated_files = await generate_image(
|
if GenType(data['type']) is GenType.IMAGE:
|
||||||
prompt=prompt,
|
generated_files = await generate_image(
|
||||||
media=media_ids, # Передаем список (пустой или с 1 фото)
|
prompt=prompt,
|
||||||
state=state,
|
media=media_ids, # Передаем список (пустой или с 1 фото)
|
||||||
dao=dao,
|
state=state,
|
||||||
bot=bot,
|
dao=dao,
|
||||||
gemini=gemini
|
bot=bot,
|
||||||
)
|
gemini=gemini
|
||||||
|
)
|
||||||
|
|
||||||
await wait_msg.delete()
|
await wait_msg.delete()
|
||||||
|
|
||||||
if generated_files:
|
if generated_files:
|
||||||
for file in generated_files:
|
for file in generated_files:
|
||||||
await message.answer_document(file, caption="✨ Generated result")
|
await message.answer_document(file, caption="✨ Generated result")
|
||||||
|
|
||||||
|
else:
|
||||||
|
await message.answer("❌ Ничего не сгенерировалось.")
|
||||||
else:
|
else:
|
||||||
await message.answer("❌ Ничего не сгенерировалось.")
|
generated_text = await gen_start_text(message=message, state=state, dao=dao, gemini=gemini, bot=bot)
|
||||||
await gen_mode_base_msg(message=message, state=state, dao=dao,call_type="start" )
|
if generated_text:
|
||||||
|
await wait_msg.edit_text(generated_text)
|
||||||
|
await gen_mode_base_msg(message=message, state=state, dao=dao, call_type="start")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await wait_msg.edit_text(f"❌ Ошибка: {e}")
|
await wait_msg.edit_text(f"❌ Ошибка: {e}")
|
||||||
@@ -291,10 +309,8 @@ async def generate_image(
|
|||||||
bot: Bot,
|
bot: Bot,
|
||||||
gemini: GoogleAdapter
|
gemini: GoogleAdapter
|
||||||
) -> List[BufferedInputFile]:
|
) -> List[BufferedInputFile]:
|
||||||
# 1. Получаем данные персонажа
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
char_id = data.get("char_id")
|
char_id = data.get("char_id")
|
||||||
|
|
||||||
if not char_id:
|
if not char_id:
|
||||||
raise ValueError("Character ID not found in state")
|
raise ValueError("Character ID not found in state")
|
||||||
|
|
||||||
@@ -303,19 +319,24 @@ async def generate_image(
|
|||||||
# Начинаем список с фото персонажа
|
# Начинаем список с фото персонажа
|
||||||
media_group_bytes = [char.character_image]
|
media_group_bytes = [char.character_image]
|
||||||
|
|
||||||
# 2. Скачиваем дополнительные файлы (если переданы)
|
|
||||||
if media:
|
if media:
|
||||||
# Создаем задачи для скачивания
|
# Скачиваем файлы
|
||||||
|
# tasks вернут список объектов BytesIO
|
||||||
tasks = [bot.download(file_id) for file_id in media]
|
tasks = [bot.download(file_id) for file_id in media]
|
||||||
|
downloaded_io = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
# Запускаем все скачивания параллельно
|
for f in downloaded_io:
|
||||||
downloaded_files = await asyncio.gather(*tasks)
|
# ОПТИМИЗАЦИЯ:
|
||||||
|
# f - это BytesIO. getvalue() копирует байты.
|
||||||
|
# Если адаптер сразу делает Image.open(io.BytesIO(bytes)),
|
||||||
|
# мы можем передать байты, но после использования 'f' (BytesIO)
|
||||||
|
# он будет удален сборщиком мусора быстрее, если мы не держим на него ссылку.
|
||||||
|
|
||||||
# Добавляем байты скачанных файлов в общий список
|
# Читаем байты и сразу забываем про объект f
|
||||||
for f in downloaded_files:
|
media_group_bytes.append(f.read())
|
||||||
media_group_bytes.append(f.getvalue())
|
f.close() # Явно закрываем поток
|
||||||
|
|
||||||
# 3. Генерация в Gemini
|
# ... вызов Gemini ...
|
||||||
generated_images_io = await asyncio.to_thread(
|
generated_images_io = await asyncio.to_thread(
|
||||||
gemini.generate_image,
|
gemini.generate_image,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
@@ -324,25 +345,32 @@ async def generate_image(
|
|||||||
quality=Quality[data['quality']],
|
quality=Quality[data['quality']],
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. Упаковка результата
|
|
||||||
images = []
|
images = []
|
||||||
if generated_images_io:
|
if generated_images_io:
|
||||||
for i, img_io in enumerate(generated_images_io):
|
for img_io in generated_images_io:
|
||||||
# Важно: img_io.read() работает корректно, если курсор в начале (adapter это делает)
|
|
||||||
images.append(
|
images.append(
|
||||||
BufferedInputFile(
|
BufferedInputFile(
|
||||||
img_io.read(),
|
img_io.read(),
|
||||||
filename=f"img_{random.randint(1000, 9999)}.png"
|
filename=f"img_{random.randint(1000, 9999)}.png"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# Важно: img_io здесь тоже BytesIO. После отправки aiogram закроет его сам,
|
||||||
|
# либо он удалится GC. Но если список generated_images_io большой,
|
||||||
|
# он висит в памяти до выхода из функции.
|
||||||
|
|
||||||
return images
|
return images
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text)
|
@router.message(F.text)
|
||||||
async def handle_text(message: Message, gemini: GoogleAdapter, bot: Bot):
|
async def handle_text(message: Message, gemini: GoogleAdapter, state: FSMContext, dao: DAO, bot: Bot):
|
||||||
|
wait_msg = await message.answer("Генерирую...")
|
||||||
|
await wait_msg.edit_text(await gen_start_text(message=message, gemini=gemini, state=state, dao=dao, bot=bot))
|
||||||
|
|
||||||
|
|
||||||
|
async def gen_start_text(message: Message, gemini: GoogleAdapter, state: FSMContext, dao: DAO, bot: Bot) -> str:
|
||||||
await bot.send_chat_action(message.chat.id, "typing")
|
await bot.send_chat_action(message.chat.id, "typing")
|
||||||
result = await asyncio.to_thread(gemini.generate, prompt=message.text)
|
result = await asyncio.to_thread(gemini.generate_text, prompt=message.text)
|
||||||
if result.get("text"):
|
if result:
|
||||||
await message.answer(result["text"], parse_mode=ParseMode.MARKDOWN)
|
return result
|
||||||
else:
|
else:
|
||||||
await message.answer("Ошибка генерации")
|
raise Exception("Ошибка генерации")
|
||||||
|
|||||||
Reference in New Issue
Block a user