update: finished for testing
This commit is contained in:
parent
630c86a92d
commit
8dc8004487
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Используем официальный образ Python в качестве базового
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Установим рабочую директорию внутри контейнера
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем файл requirements.txt в контейнер
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Устанавливаем зависимости из requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Копируем весь код проекта в контейнер
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Указываем команду для запуска вашего приложения
|
||||||
|
CMD ["python", "app.py"]
|
4
app.py
4
app.py
@ -3,16 +3,16 @@ from aiogram import Dispatcher, Bot
|
|||||||
from aiogram.client.default import DefaultBotProperties
|
from aiogram.client.default import DefaultBotProperties
|
||||||
from config import *
|
from config import *
|
||||||
from bot.handlers.user.user_handlers import user
|
from bot.handlers.user.user_handlers import user
|
||||||
from bot.handlers.admin.admin_handlers import admin
|
from db.db import *
|
||||||
|
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
dp.include_router(user)
|
dp.include_router(user)
|
||||||
dp.include_router(admin)
|
|
||||||
|
|
||||||
bot = Bot(token=bot_token, default=DefaultBotProperties(parse_mode='HTML'))
|
bot = Bot(token=bot_token, default=DefaultBotProperties(parse_mode='HTML'))
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
await init_db()
|
||||||
await dp.start_polling(bot)
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
from aiogram import Router
|
|
||||||
|
|
||||||
|
|
||||||
admin = Router()
|
|
@ -1,25 +1,26 @@
|
|||||||
from aiogram.filters.callback_data import CallbackData
|
from aiogram.filters.callback_data import CallbackData
|
||||||
from aiogram.types import InlineKeyboardButton
|
from aiogram.types import InlineKeyboardButton
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
from db.db import *
|
||||||
|
|
||||||
models_description = {
|
models_description = {
|
||||||
"1o": "Это базовая модель ИИ, которая \"умеет думать\". Она хороша для выполнения повседневных задач, \
|
"1o": "Это базовая модель ИИ, которая \"умеет думать\". Она хороша для выполнения повседневных задач, \
|
||||||
таких как анализ данных, обработка текста и создание отчетов. Она подходит для малого и среднего бизнеса, \
|
таких как анализ данных, обработка текста и создание отчетов. Она подходит для малого и среднего бизнеса, \
|
||||||
где важно автоматизировать рутинные задачи и получать быстрые результаты.\n\n\
|
где важно автоматизировать рутинные задачи и получать быстрые результаты.\n\n\
|
||||||
Выберите тариф к модели 1о.",
|
Выберите тариф к модели 1о.\nОставшиеся токены: ",
|
||||||
"1o mini": "Уменьшенная версия 1о, которая также \"умеет думать\". Эта модель менее мощная, \
|
"1o mini": "Уменьшенная версия 1о, которая также \"умеет думать\". Эта модель менее мощная, \
|
||||||
но зато работает быстрее на устройствах с ограниченными ресурсами, таких как смартфоны или небольшие компьютеры. \
|
но зато работает быстрее на устройствах с ограниченными ресурсами, таких как смартфоны или небольшие компьютеры. \
|
||||||
Прекрасно подойдет для мобильных приложений и стартапов, где ресурсы ограничены, но нужны умные решения.\n\n\
|
Прекрасно подойдет для мобильных приложений и стартапов, где ресурсы ограничены, но нужны умные решения.\n\n\
|
||||||
Выберите тариф к модели 1о mini.",
|
Выберите тариф к модели 1о mini.\nОставшиеся токены: ",
|
||||||
"4o": "Это мощная модель ИИ, предназначенная для сложных и объемных задач. Она хорошо справляется \
|
"4o": "Это мощная модель ИИ, предназначенная для сложных и объемных задач. Она хорошо справляется \
|
||||||
с глубоким анализом больших данных, сложными прогнозами и инновационными бизнес-проектами. \
|
с глубоким анализом больших данных, сложными прогнозами и инновационными бизнес-проектами. \
|
||||||
Идеальна для крупных компаний, требующих высокую производительность для динамичного принятия решений и стратегического анализа.\n\n\
|
Идеальна для крупных компаний, требующих высокую производительность для динамичного принятия решений и стратегического анализа.\n\n\
|
||||||
Выберите тариф к модели 4о.",
|
Выберите тариф к модели 4о.\nОставшиеся токены: ",
|
||||||
"4o mini": "Уменьшенная версия модели 4о. Она сохраняет высокую мощность при меньшем использовании ресурсов. \
|
"4o mini": "Уменьшенная версия модели 4о. Она сохраняет высокую мощность при меньшем использовании ресурсов. \
|
||||||
Подходит для сложных бизнес-задач в условиях ограниченных ресурсов, таких как аналитика в режиме реального времени \
|
Подходит для сложных бизнес-задач в условиях ограниченных ресурсов, таких как аналитика в режиме реального времени \
|
||||||
на портативных и мобильных устройствах. Отличный выбор для бизнеса, который хочет получить максимум от своих технологий \
|
на портативных и мобильных устройствах. Отличный выбор для бизнеса, который хочет получить максимум от своих технологий \
|
||||||
без больших затрат на оборудование.\n\n\
|
без больших затрат на оборудование.\n\n\
|
||||||
Выберите тариф к модели 4о."
|
Выберите тариф к модели 4о.\nОставшиеся токены: "
|
||||||
}
|
}
|
||||||
|
|
||||||
model_prices = {
|
model_prices = {
|
||||||
@ -87,23 +88,35 @@ name_of_model = {
|
|||||||
"pro" : "Профессиональный"
|
"pro" : "Профессиональный"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BuyCallBack(CallbackData, prefix="buy"):
|
||||||
|
name: str
|
||||||
|
option_of_model: str
|
||||||
|
tokens: str
|
||||||
|
amount: str
|
||||||
|
|
||||||
class PayCallBack(CallbackData, prefix="pay"):
|
class PayCallBack(CallbackData, prefix="pay"):
|
||||||
name: str
|
name: str
|
||||||
option_of_model: str
|
option_of_model: str
|
||||||
tokens: str
|
tokens: str
|
||||||
amount: str
|
amount: str
|
||||||
|
|
||||||
def get_model_page(*, page: int, name: str):
|
class ChooseCallBack(CallbackData, prefix="choose"):
|
||||||
text: str = models_description[name]
|
name: str
|
||||||
|
|
||||||
|
async def get_model_page(*, page: int, name: str, user_id):
|
||||||
|
tokens = await get_current_model_tokens(user_id, name)
|
||||||
|
status = await get_current_model(user_id) == name
|
||||||
|
|
||||||
|
text: str = models_description[name] + str(tokens)
|
||||||
|
|
||||||
temp_kbds: InlineKeyboardBuilder = InlineKeyboardBuilder()
|
temp_kbds: InlineKeyboardBuilder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
for option_name, item in model_prices[name].items():
|
for option_name, item in model_prices[name].items():
|
||||||
option_name_rus, tokens, amount = name_of_model[option_name], item["tokens"], item["amount"]
|
option_name_rus, tokens, amount = name_of_model[option_name], item["tokens"], item["amount"]
|
||||||
temp_text = f"{name_of_model[option_name]} | {tokens} запросов за {amount} RUB"
|
temp_text = f"{name_of_model[option_name]} | {tokens} запросов"
|
||||||
temp_kbds.add(InlineKeyboardButton(
|
temp_kbds.add(InlineKeyboardButton(
|
||||||
text=temp_text,
|
text=temp_text,
|
||||||
callback_data=PayCallBack(
|
callback_data=BuyCallBack(
|
||||||
name=name,
|
name=name,
|
||||||
option_of_model=option_name_rus,
|
option_of_model=option_name_rus,
|
||||||
tokens=tokens,
|
tokens=tokens,
|
||||||
@ -111,6 +124,13 @@ def get_model_page(*, page: int, name: str):
|
|||||||
).pack()
|
).pack()
|
||||||
))
|
))
|
||||||
|
|
||||||
|
temp_kbds.add(InlineKeyboardButton(
|
||||||
|
text="🟢 Выбрано" if status else "🔴 Не выбрано",
|
||||||
|
callback_data=ChooseCallBack(
|
||||||
|
name=name,
|
||||||
|
).pack()
|
||||||
|
))
|
||||||
|
|
||||||
return text, temp_kbds
|
return text, temp_kbds
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,8 +97,8 @@ async def tarif_page(page: int, name: str):
|
|||||||
|
|
||||||
return text, kbds
|
return text, kbds
|
||||||
|
|
||||||
async def models_description_page(page: int, name: str):
|
async def models_description_page(page: int, name: str, user_id):
|
||||||
text, kbds = get_user_model_description_page(page=page, name=name)
|
text, kbds = await get_user_model_description_page(page=page, name=name, user_id=user_id)
|
||||||
|
|
||||||
return text, kbds
|
return text, kbds
|
||||||
|
|
||||||
@ -114,7 +114,9 @@ async def price_list_page(page: int, name: str):
|
|||||||
|
|
||||||
return text, kbds
|
return text, kbds
|
||||||
|
|
||||||
async def get_page_content(page: int, name: str):
|
|
||||||
|
|
||||||
|
async def get_page_content(page: int, name: str, user_id):
|
||||||
|
|
||||||
if page == 0:
|
if page == 0:
|
||||||
return await main_page(page, name)
|
return await main_page(page, name)
|
||||||
@ -125,7 +127,7 @@ async def get_page_content(page: int, name: str):
|
|||||||
elif page == 3:
|
elif page == 3:
|
||||||
return await more_about_models_page(page, name)
|
return await more_about_models_page(page, name)
|
||||||
elif 4 <= page <= 7:
|
elif 4 <= page <= 7:
|
||||||
return await models_description_page(page, name)
|
return await models_description_page(page, name, user_id)
|
||||||
elif page == 8:
|
elif page == 8:
|
||||||
#TODO
|
#TODO
|
||||||
...
|
...
|
||||||
|
7
bot/handlers/user/states.py
Normal file
7
bot/handlers/user/states.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
|
|
||||||
|
class ChooseModelState(StatesGroup):
|
||||||
|
choosing_model = State()
|
||||||
|
|
||||||
|
class SendMessageState(StatesGroup):
|
||||||
|
waiting_for_message = State()
|
@ -1,40 +1,118 @@
|
|||||||
from aiogram import Router, types
|
import csv
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from bot.handlers.user.states import ChooseModelState, SendMessageState
|
||||||
|
import time
|
||||||
|
from db.db import *
|
||||||
|
from aiogram import Router, types, F
|
||||||
from aiogram.filters import CommandStart, Command
|
from aiogram.filters import CommandStart, Command
|
||||||
from aiogram.types import LabeledPrice, PreCheckoutQuery
|
from aiogram.types import LabeledPrice, PreCheckoutQuery, InlineKeyboardButton, FSInputFile
|
||||||
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
from bot.handlers.user.model_processing import PayCallBack
|
from db.db import user_exists, add_user_to_db, set_curren_model, get_temp_prompts
|
||||||
|
from bot.handlers.user.model_processing import PayCallBack, BuyCallBack, ChooseCallBack
|
||||||
from bot.handlers.user.page_processing import get_page_content
|
from bot.handlers.user.page_processing import get_page_content
|
||||||
from bot.kbs.inline import PageCallBack
|
from bot.kbs.inline import PageCallBack
|
||||||
from config import shop_api_token
|
from config import shop_api_token, admins
|
||||||
|
from bot.utils.openai_tasks import get_model_suggestion_from_openai, get_answer_to_question
|
||||||
|
|
||||||
user = Router()
|
user = Router()
|
||||||
|
|
||||||
@user.message(CommandStart())
|
@user.message(CommandStart())
|
||||||
async def start(message: types.Message):
|
async def start(message: types.Message, state: FSMContext):
|
||||||
text, reply_markup = await get_page_content(page=0,name="main")
|
await state.clear()
|
||||||
|
# TODO
|
||||||
|
isAdded = await user_exists(message.from_user.id)
|
||||||
|
if not isAdded:
|
||||||
|
# Извлекаем информацию о пользователе
|
||||||
|
first_name = message.from_user.first_name or ""
|
||||||
|
last_name = message.from_user.last_name or ""
|
||||||
|
user_name = message.from_user.username or ""
|
||||||
|
|
||||||
|
# Добавляем пользователя в базу данных
|
||||||
|
await add_user_to_db(
|
||||||
|
user_id=message.from_user.id,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
user_name=user_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
text, reply_markup = await get_page_content(page=0,name="main", user_id=message.from_user.id)
|
||||||
|
|
||||||
await message.answer(text, reply_markup=reply_markup)
|
await message.answer(text, reply_markup=reply_markup)
|
||||||
|
|
||||||
|
|
||||||
@user.callback_query(PageCallBack.filter())
|
@user.callback_query(PageCallBack.filter())
|
||||||
async def user_pages(callback: types.CallbackQuery, callback_data: PageCallBack):
|
async def user_pages(callback: types.CallbackQuery, callback_data: PageCallBack, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
text, reply_markup = await get_page_content(
|
text, reply_markup = await get_page_content(
|
||||||
page=callback_data.page,
|
page=callback_data.page,
|
||||||
name=callback_data.page_name,
|
name=callback_data.page_name,
|
||||||
|
user_id=callback.from_user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=reply_markup)
|
await callback.message.edit_text(text, reply_markup=reply_markup)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
@user.callback_query(PayCallBack.filter())
|
|
||||||
async def buy(callback: types.CallbackQuery, callback_data: PayCallBack):
|
|
||||||
|
|
||||||
await callback.message.bot.send_invoice(
|
@user.callback_query(ChooseCallBack.filter())
|
||||||
|
async def choose(callback: types.CallbackQuery, callback_data: ChooseCallBack):
|
||||||
|
await set_curren_model(user_id=callback.from_user.id, name_of_model=callback_data.name)
|
||||||
|
|
||||||
|
text, reply_markup = await get_page_content(
|
||||||
|
page=2,
|
||||||
|
user_id=callback.from_user.id,
|
||||||
|
name='tarif',
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=reply_markup)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
@user.message(Command(commands=['tarif']))
|
||||||
|
async def tarif(message: types.Message):
|
||||||
|
# Получаем текст и клавиатуру для страницы тарифа
|
||||||
|
text, reply_markup = await get_page_content(
|
||||||
|
page=2,
|
||||||
|
user_id=message.from_user.id, # Используем message.from_user.id вместо callback
|
||||||
|
name='tarif',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Отправляем новое сообщение с текстом и кнопками
|
||||||
|
await message.answer(text, reply_markup=reply_markup)
|
||||||
|
|
||||||
|
@user.callback_query(BuyCallBack.filter())
|
||||||
|
async def buy(callback: types.CallbackQuery, callback_data: BuyCallBack):
|
||||||
|
kbds = InlineKeyboardBuilder()
|
||||||
|
buttons = [
|
||||||
|
InlineKeyboardButton(text="Купить", callback_data=PayCallBack(
|
||||||
|
name=callback_data.name,
|
||||||
|
option_of_model=callback_data.option_of_model,
|
||||||
|
tokens=callback_data.tokens,
|
||||||
|
amount=callback_data.amount
|
||||||
|
).pack()),
|
||||||
|
InlineKeyboardButton(text="⬅️Назад", callback_data=PageCallBack(page=2, page_name="tarif").pack()),
|
||||||
|
]
|
||||||
|
|
||||||
|
for button in buttons:
|
||||||
|
kbds.add(button)
|
||||||
|
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text = f"Модель: {callback_data.name}\nТариф: {callback_data.option_of_model}\nКол-ов запросов: {callback_data.tokens}\nСтоимость: {callback_data.amount}RUB",
|
||||||
|
reply_markup=kbds.adjust(*(1,)).as_markup()
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@user.callback_query(PayCallBack.filter())
|
||||||
|
async def pay(callback: types.CallbackQuery, callback_data: PayCallBack):
|
||||||
|
temp = await callback.message.bot.send_invoice(
|
||||||
chat_id=callback.from_user.id,
|
chat_id=callback.from_user.id,
|
||||||
description=f'{callback_data.tokens} токенов за {callback_data.amount} RUB для модели {callback_data.name}',
|
description=f'{callback_data.tokens} токенов за {callback_data.amount} RUB для модели {callback_data.name}',
|
||||||
title=f'{callback_data.option_of_model} подписка на {callback_data.name}',
|
title=f'{callback_data.option_of_model} подписка на {callback_data.name}',
|
||||||
payload=f'sub_{callback_data.name}',
|
payload=f'sub_{callback_data.name}_{callback.from_user.id}_{callback_data.tokens}',
|
||||||
|
# Добавляем id пользователя и количество токенов в payload
|
||||||
provider_token=shop_api_token,
|
provider_token=shop_api_token,
|
||||||
start_parameter='test',
|
start_parameter='test',
|
||||||
currency="RUB",
|
currency="RUB",
|
||||||
@ -44,10 +122,227 @@ async def buy(callback: types.CallbackQuery, callback_data: PayCallBack):
|
|||||||
)],
|
)],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# if temp.successful_payment is None:
|
||||||
|
# await callback.bot.send_message(chat_id=callback.from_user.id,text="Something went wrong")
|
||||||
|
|
||||||
|
|
||||||
@user.pre_checkout_query()
|
@user.pre_checkout_query()
|
||||||
async def procces_pre_checkout_query(pre_checkout_query: PreCheckoutQuery):
|
async def process_pre_checkout_query(pre_checkout_query: PreCheckoutQuery):
|
||||||
await pre_checkout_query.answer(ok=True)
|
await pre_checkout_query.answer(ok=True)
|
||||||
|
|
||||||
|
# time.sleep(2)
|
||||||
|
#
|
||||||
|
# await pre_checkout_query.bot.send_message(chat_id=pre_checkout_query.from_user.id, text="Спасибо, пользутесь нашим сервисом!")
|
||||||
|
#
|
||||||
|
# text, reply_markup = await get_page_content(page=0, name="main", user_id=pre_checkout_query.from_user.id)
|
||||||
|
#
|
||||||
|
# await pre_checkout_query.bot.send_message(
|
||||||
|
# chat_id=pre_checkout_query.from_user.id,
|
||||||
|
# text=text,
|
||||||
|
# reply_markup=reply_markup,
|
||||||
|
# )
|
||||||
|
|
||||||
|
@user.message(F.successful_payment)
|
||||||
|
async def process_payment(message: types.Message):
|
||||||
|
|
||||||
|
if message.successful_payment is None:
|
||||||
|
await message.bot.send_message(chat_id=message.from_user.id,text="Что то пошло не так")
|
||||||
|
return
|
||||||
|
|
||||||
|
payload_data = message.successful_payment.invoice_payload.split('_')
|
||||||
|
if len(payload_data) != 4:
|
||||||
|
return # Не валидный payload
|
||||||
|
|
||||||
|
model_name = payload_data[1] # Имя модели
|
||||||
|
user_id = int(payload_data[2]) # ID пользователя
|
||||||
|
tokens = int(payload_data[3]) # Количество токенов
|
||||||
|
|
||||||
|
await add_tokens_to_user(user_id, model_name, tokens)
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
await message.bot.send_message(chat_id=message.from_user.id,
|
||||||
|
text="Спасибо, что пользуетесь нашим сервисом!")
|
||||||
|
|
||||||
|
await message.bot.send_message(chat_id=message.from_user.id,
|
||||||
|
text="Для работы с chatGPT отправьте текст с вашим вопросом и получите на него ответ)")
|
||||||
|
|
||||||
|
@user.message(Command(commands=["failed"]))
|
||||||
|
async def process_payment(message: types.Message):
|
||||||
|
await message.bot.send_message(chat_id=message.from_user.id,
|
||||||
|
text="Что-то пошло не так( Попробуйте еще раз!")
|
||||||
|
|
||||||
|
text, reply_markup = await get_page_content(page=0, name="main", user_id=message.from_user.id)
|
||||||
|
|
||||||
|
await message.bot.send_message(
|
||||||
|
chat_id=message.from_user.id,
|
||||||
|
text=text,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@user.callback_query(lambda c: c.data == 'choose')
|
||||||
|
async def chat_test_gpt(callback: types.CallbackQuery, state: FSMContext):
|
||||||
|
kbds = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
kbds.add(InlineKeyboardButton(
|
||||||
|
text="⬅️Назад",
|
||||||
|
callback_data=PageCallBack(
|
||||||
|
page=3,
|
||||||
|
page_name='more_about_models',
|
||||||
|
).pack()
|
||||||
|
))
|
||||||
|
|
||||||
|
await callback.message.edit_text("Вы находитесь в разделе подбора модели. Опишите ваши задачи, и я помогу выбрать модель.", reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
await state.set_state(ChooseModelState.choosing_model)
|
||||||
|
|
||||||
|
@user.message(ChooseModelState.choosing_model)
|
||||||
|
async def process_model_question(message: types.Message):
|
||||||
|
user_id = message.from_user.id
|
||||||
|
|
||||||
|
remaining_queries = await get_temp_prompts(user_id=user_id)
|
||||||
|
|
||||||
|
kbds = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
kbds.add(InlineKeyboardButton(
|
||||||
|
text="⬅️Назад",
|
||||||
|
callback_data=PageCallBack(
|
||||||
|
page=3,
|
||||||
|
page_name='more_about_models'
|
||||||
|
).pack()
|
||||||
|
))
|
||||||
|
|
||||||
|
if remaining_queries > 0:
|
||||||
|
# Обрабатываем вопрос с помощью OpenAI GPT
|
||||||
|
openai_response = await get_model_suggestion_from_openai(message.text)
|
||||||
|
|
||||||
|
# Отправляем ответ пользователю
|
||||||
|
# await message.answer(text=f"Ответ OpenAI: {openai_response}" + f"\nОсталось {remaining_queries-1} запросов.", reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
|
||||||
|
# Уменьшаем количество запросов
|
||||||
|
if "Ошибка при обращении к OpenAI" not in openai_response:
|
||||||
|
await message.answer(
|
||||||
|
text=f"Ответ OpenAI: {openai_response}" + f"\nОсталось {remaining_queries - 1} запросов.",
|
||||||
|
reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
|
||||||
|
# await decrease_test_queries(user_id)
|
||||||
|
...
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
text=f"Попробуйте еще раз или чуть подождите!")
|
||||||
|
else:
|
||||||
|
await message.answer(text="У вас закончились доступные запросы для подбора модели.", reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
|
||||||
|
|
||||||
|
@user.message(Command(commands=['export_users']))
|
||||||
|
async def send_csv_to_admin(message: types.Message):
|
||||||
|
# Генерируем CSV файл
|
||||||
|
if message.from_user.id in admins:
|
||||||
|
users = await get_all_users()
|
||||||
|
|
||||||
|
# Генерируем CSV файл
|
||||||
|
with open('data/users.csv', 'w', newline='', encoding='utf-8') as csvfile:
|
||||||
|
csvwriter = csv.writer(csvfile, delimiter=';', quoting=csv.QUOTE_MINIMAL)
|
||||||
|
csvwriter.writerow(['ID', 'First Name', 'Last Name', 'Username'])
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
csvwriter.writerow([user.user_id, user.first_name, user.last_name, user.user_name])
|
||||||
|
|
||||||
|
# Отправляем CSV файл администратору
|
||||||
|
csv_file = FSInputFile('data/users.csv') # Обернуть путь к файлу как InputFile
|
||||||
|
await message.answer_document(csv_file)
|
||||||
|
|
||||||
|
|
||||||
|
@user.message(Command(commands=['send_message_to_all']))
|
||||||
|
async def send_messages_to_users(message: types.Message, state: FSMContext):
|
||||||
|
if message.from_user.id in admins:
|
||||||
|
# Устанавливаем состояние, что бот ждет текст сообщения для рассылки
|
||||||
|
await state.set_state(SendMessageState.waiting_for_message)
|
||||||
|
|
||||||
|
await message.answer("Введите сообщение для рассылки:")
|
||||||
|
|
||||||
|
|
||||||
|
@user.message(SendMessageState.waiting_for_message)
|
||||||
|
async def get_message_for_users(msg: types.Message, state: FSMContext):
|
||||||
|
# Получаем текст сообщения для рассылки
|
||||||
|
text_to_send = msg.text
|
||||||
|
|
||||||
|
users = await get_all_users()
|
||||||
|
|
||||||
|
# Рассылаем сообщение всем пользователям
|
||||||
|
for user in users:
|
||||||
|
if user.user_id == msg.from_user.id:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
await msg.bot.send_message(chat_id=user.user_id, text=f'Сообщение от админа:\n{text_to_send}')
|
||||||
|
except Exception as e:
|
||||||
|
# Логируем ошибки для недоступных пользователей
|
||||||
|
print(f"Не удалось отправить сообщение пользователю {user.user_id}: {e}")
|
||||||
|
|
||||||
|
await msg.answer("Сообщение успешно отправлено всем пользователям.")
|
||||||
|
|
||||||
|
# Очистить состояние после завершения рассылки
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@user.message(~Command(commands=["export_users", 'send_message_to_all'])) # Игнорируем команды
|
||||||
|
async def chatgpt_chatting(message: types.Message):
|
||||||
|
user_id = message.from_user.id
|
||||||
|
|
||||||
|
db_model = await get_current_model(user_id)
|
||||||
|
|
||||||
|
kbds = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
kbds.add(InlineKeyboardButton(
|
||||||
|
text="Выбрать тариф",
|
||||||
|
callback_data=PageCallBack(
|
||||||
|
page=2,
|
||||||
|
page_name='tarif'
|
||||||
|
).pack()))
|
||||||
|
|
||||||
|
kbds.add(InlineKeyboardButton(
|
||||||
|
text="⬅️На главную",
|
||||||
|
callback_data=PageCallBack(
|
||||||
|
page=0,
|
||||||
|
page_name='main'
|
||||||
|
).pack()
|
||||||
|
))
|
||||||
|
|
||||||
|
if db_model is None:
|
||||||
|
await message.bot.send_message(chat_id=message.from_user.id, text="У вас еще не выбрана модель", reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'1o' : "o1-preview",
|
||||||
|
'1o mini': "o1-mini",
|
||||||
|
'4o': "gpt-4o",
|
||||||
|
'4o mini': "gpt-4o-mini"
|
||||||
|
}
|
||||||
|
|
||||||
|
current_model = models[db_model]
|
||||||
|
|
||||||
|
remaining_queries = await get_current_model_tokens(user_id=user_id, name_of_model=db_model)
|
||||||
|
|
||||||
|
if remaining_queries > 0:
|
||||||
|
openai_response = await get_answer_to_question(message.text, current_model)
|
||||||
|
|
||||||
|
# Уменьшаем количество запросов
|
||||||
|
if "Ошибка при обращении к OpenAI" not in openai_response:
|
||||||
|
await decrease_current_model_tokens(user_id, name_of_model=db_model)
|
||||||
|
await message.answer(text=f"Ответ OpenAI: {openai_response}")
|
||||||
|
await message.answer(text=f"Модель: {current_model}\nОсталось {remaining_queries - 1} запросов.",
|
||||||
|
reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
else:
|
||||||
|
await message.answer(
|
||||||
|
text=f"{openai_response}")
|
||||||
|
await message.answer(
|
||||||
|
text=f"Попробуйте еще раз или вы можете выбрать другую доступную для вас модель!")
|
||||||
|
|
||||||
|
else:
|
||||||
|
await message.answer(text=f"У вас закончились доступные запросы для модели {current_model}. Купите новые токены или выберете другую модель",
|
||||||
|
reply_markup=kbds.adjust(*(1,)).as_markup())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ from aiogram.filters.callback_data import CallbackData
|
|||||||
from aiogram.types import InlineKeyboardButton
|
from aiogram.types import InlineKeyboardButton
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from bot.handlers.user.model_processing import get_model_page
|
from bot.handlers.user.model_processing import get_model_page, ChooseCallBack
|
||||||
|
|
||||||
|
|
||||||
class PageCallBack(CallbackData, prefix="page"):
|
class PageCallBack(CallbackData, prefix="page"):
|
||||||
@ -38,8 +38,8 @@ def get_user_tarif_page(*, page): # page 2
|
|||||||
|
|
||||||
return get_kb_buttons(btns=btns)
|
return get_kb_buttons(btns=btns)
|
||||||
|
|
||||||
def get_user_model_description_page(*, page: int, name: str):
|
async def get_user_model_description_page(*, page: int, name: str, user_id):
|
||||||
text, kbd = get_model_page(page=page, name=name)
|
text, kbd = await get_model_page(page=page, name=name, user_id=user_id)
|
||||||
|
|
||||||
kbd.add(InlineKeyboardButton(text="⬅️Назад", callback_data=PageCallBack(page=2, page_name="tarif").pack()),)
|
kbd.add(InlineKeyboardButton(text="⬅️Назад", callback_data=PageCallBack(page=2, page_name="tarif").pack()),)
|
||||||
|
|
||||||
@ -47,7 +47,8 @@ def get_user_model_description_page(*, page: int, name: str):
|
|||||||
|
|
||||||
def get_more_about_modules_page(*, page: int): # page 3
|
def get_more_about_modules_page(*, page: int): # page 3
|
||||||
btns = [
|
btns = [
|
||||||
InlineKeyboardButton(text="Как узнать, какая модель мне подходит", callback_data=PageCallBack(page=8, page_name='match_model').pack()),
|
# InlineKeyboardButton(text="Какая модель мне подходит", callback_data=PageCallBack(page=8, page_name='match_model').pack()),
|
||||||
|
InlineKeyboardButton(text="Как узнать, какая модель мне подходит", callback_data='choose'),
|
||||||
InlineKeyboardButton(text="Узнать стоимость моделей", callback_data=PageCallBack(page=9, page_name='price_list').pack()),
|
InlineKeyboardButton(text="Узнать стоимость моделей", callback_data=PageCallBack(page=9, page_name='price_list').pack()),
|
||||||
InlineKeyboardButton(text="Выбрать модель", callback_data=PageCallBack(page=2, page_name="tarif").pack()), # 2
|
InlineKeyboardButton(text="Выбрать модель", callback_data=PageCallBack(page=2, page_name="tarif").pack()), # 2
|
||||||
InlineKeyboardButton(text="⬅️Назад", callback_data=PageCallBack(page=0, page_name="main").pack()),
|
InlineKeyboardButton(text="⬅️Назад", callback_data=PageCallBack(page=0, page_name="main").pack()),
|
||||||
|
71
bot/utils/openai_tasks.py
Normal file
71
bot/utils/openai_tasks.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
from openai import AsyncOpenAI
|
||||||
|
from config import gpt_api_key
|
||||||
|
|
||||||
|
aclient = AsyncOpenAI(api_key=gpt_api_key)
|
||||||
|
|
||||||
|
# Инициализация клиента OpenAI
|
||||||
|
|
||||||
|
async def get_model_suggestion_from_openai(user_question: str) -> str:
|
||||||
|
# Формирование сообщения для чата
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": (
|
||||||
|
"Ты работаешь как консультант по выбору тарифов для ChatGPT. "
|
||||||
|
"Тарифы называются 1o, 1o mini, 4o, 4o mini, и они представляют собой количество запросов или мощность обработки модели. "
|
||||||
|
"Твоя задача — помочь пользователям выбрать подходящий тариф."
|
||||||
|
"1o: Для автоматизации рутинных задач (анализ данных, обработка текста). Подходит малым и средним бизнесам."
|
||||||
|
"1o mini: Оптимизирован для мобильных устройств, подходит для стартапов с ограниченными ресурсами."
|
||||||
|
"4o: Для глубокого анализа и сложных прогнозов. Идеальна для крупных компаний с высокими требованиями."
|
||||||
|
"4o mini: Высокая мощность при низком использовании ресурсов. Хороша для аналитики в реальном времени на мобильных устройствах."
|
||||||
|
"Помоги ему выбрать подходящую модель на основе описаний. Ответ должен быть до 300 символов и касаться только выбора модели. На все другие вопросы скажи, что они не по теме."
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": (
|
||||||
|
f"Пользователь задал вопрос: {user_question}. "
|
||||||
|
"Он хочет выбрать подходящий тариф для ChatGPT среди 1o, 1o mini, 4o, 4o mini. "
|
||||||
|
"Опиши, какой тариф лучше всего подходит для его задач."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Используем эндпоинт для чата
|
||||||
|
response = await aclient.chat.completions.create(
|
||||||
|
model="gpt-4",
|
||||||
|
messages=messages,
|
||||||
|
n=1,
|
||||||
|
stop=None,
|
||||||
|
temperature=0.7
|
||||||
|
)
|
||||||
|
response_message = response.choices[0].message.content
|
||||||
|
return response_message
|
||||||
|
except Exception as e:
|
||||||
|
return f"Ошибка при обращении к OpenAI: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
|
async def get_answer_to_question(question: str, name_of_model: str):
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "Ты — помощник, который отвечает на вопросы пользователя. Ты даешь развернутые и ценные ответы, которые должны отвечать на вопрос пользователя"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": question
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await aclient.chat.completions.create(
|
||||||
|
model=name_of_model,
|
||||||
|
messages=messages,
|
||||||
|
n=1,
|
||||||
|
stop=None,
|
||||||
|
temperature=0.7
|
||||||
|
)
|
||||||
|
return response.choices[0].message.content
|
||||||
|
except Exception as e:
|
||||||
|
return f"Ошибка при обращении к OpenAI: {str(e)}"
|
13
config.py
13
config.py
@ -4,7 +4,18 @@ from dotenv import load_dotenv
|
|||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
bot_token = os.getenv("BOT_TOKEN")
|
bot_token = os.getenv("BOT_TOKEN")
|
||||||
admins = [367757357]
|
admins = [367757357, 1617340397, 5899041406]
|
||||||
|
|
||||||
|
|
||||||
|
# Теперь вы можете получить доступ к переменным окружения
|
||||||
|
postgres_user = os.getenv('POSTGRES_USER')
|
||||||
|
postgres_password = os.getenv('POSTGRES_PASSWORD')
|
||||||
|
postgres_db = os.getenv('POSTGRES_DB')
|
||||||
|
|
||||||
|
# Формируем строку подключения
|
||||||
|
DATABASE_URL = f"postgresql+asyncpg://{postgres_user}:{postgres_password}@db:5432/{postgres_db}"
|
||||||
|
|
||||||
shop_id = os.getenv("SHOP_ID")
|
shop_id = os.getenv("SHOP_ID")
|
||||||
shop_api_token = os.getenv("SHOP_API_TOKEN")
|
shop_api_token = os.getenv("SHOP_API_TOKEN")
|
||||||
|
|
||||||
|
gpt_api_key = os.getenv("GPT_API_KEY")
|
3
data/users.csv
Normal file
3
data/users.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ID;First Name;Last Name;Username
|
||||||
|
5411565044;Notimy;;notimy_official
|
||||||
|
367757357;4hellboy4;;pushkin404
|
|
201
db/db.py
Normal file
201
db/db.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
|
||||||
|
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, BigInteger
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.future import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, relationship
|
||||||
|
from config import DATABASE_URL
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = 'users'
|
||||||
|
|
||||||
|
user_id = Column(BigInteger, primary_key=True, unique=True, index=True) # уникальный ID пользователя из aiogram
|
||||||
|
first_name = Column(String)
|
||||||
|
last_name = Column(String)
|
||||||
|
user_name = Column(String)
|
||||||
|
test_queries = Column(Integer) # временные промпты
|
||||||
|
current_model = Column(String)
|
||||||
|
|
||||||
|
subscriptions = relationship("Subscription", back_populates="user", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
class Subscription(Base):
|
||||||
|
__tablename__ = 'subscriptions'
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
user_id = Column(BigInteger, ForeignKey('users.user_id'), nullable=False)
|
||||||
|
purchased_model = Column(String, nullable=False)
|
||||||
|
purchased_tokens = Column(Integer)
|
||||||
|
|
||||||
|
user = relationship("User", back_populates="subscriptions")
|
||||||
|
|
||||||
|
engine = create_async_engine(DATABASE_URL)
|
||||||
|
async_session = sessionmaker(bind=engine, class_=AsyncSession)
|
||||||
|
|
||||||
|
async def init_db() -> None:
|
||||||
|
async with engine.begin() as conn:
|
||||||
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
|
|
||||||
|
async def get_all_users():
|
||||||
|
async with engine.connect() as connection:
|
||||||
|
result = await connection.execute(select(User))
|
||||||
|
users = result.fetchall() # Получаем всех пользователей
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
async def add_user_to_db(user_id: int, first_name: str, last_name: str, user_name: str) -> None:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
user = User(
|
||||||
|
user_id=user_id,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
user_name=user_name,
|
||||||
|
test_queries=10, # количество временных промптов
|
||||||
|
current_model=""
|
||||||
|
)
|
||||||
|
session.add(user)
|
||||||
|
await session.commit()
|
||||||
|
return
|
||||||
|
|
||||||
|
async def user_exists(user_id: int) -> bool:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
return user is not None
|
||||||
|
|
||||||
|
async def decrease_test_queries(user_id: int) -> None:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
user.test_queries -= 1 if user.test_queries > 0 else 0
|
||||||
|
await session.commit()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_temp_prompts(user_id: int):
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
return user.test_queries
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
async def set_curren_model(user_id: int, name_of_model: str) -> None:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
user.current_model = name_of_model
|
||||||
|
await session.commit()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def add_tokens_to_user(user_id: int, name_of_model: str, queries: int) -> None:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
# Проверяем, есть ли подписка на модель
|
||||||
|
subscription_result = await session.execute(
|
||||||
|
select(Subscription).where(
|
||||||
|
Subscription.user_id == user_id,
|
||||||
|
Subscription.purchased_model == name_of_model
|
||||||
|
)
|
||||||
|
)
|
||||||
|
subscription = subscription_result.scalar()
|
||||||
|
|
||||||
|
if subscription:
|
||||||
|
# Подписка существует, обновляем количество токенов
|
||||||
|
subscription.purchased_tokens += queries # Предполагается, что поле tokens существует в модели Subscription
|
||||||
|
await session.commit() # Сохраняем изменения в базе данных
|
||||||
|
else:
|
||||||
|
# Подписка не существует, создаем новую
|
||||||
|
new_subscription = Subscription(
|
||||||
|
user_id=user_id,
|
||||||
|
purchased_model=name_of_model,
|
||||||
|
purchased_tokens=queries
|
||||||
|
)
|
||||||
|
session.add(new_subscription)
|
||||||
|
await session.commit() # Сохраняем изменения в базе данных
|
||||||
|
else:
|
||||||
|
# Пользователь не найден, можно обработать эту ситуацию
|
||||||
|
print(f"Пользователь с ID {user_id} не найден.")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_model(user_id: int) -> str:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
return user.current_model if user else ""
|
||||||
|
|
||||||
|
async def get_current_model_tokens(user_id: int, name_of_model: str) -> int:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
subscription_result = await session.execute(
|
||||||
|
select(Subscription).where(Subscription.user_id == user_id,
|
||||||
|
Subscription.purchased_model == name_of_model)
|
||||||
|
)
|
||||||
|
subscription = subscription_result.scalar()
|
||||||
|
|
||||||
|
if subscription:
|
||||||
|
return subscription.purchased_tokens
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
async def decrease_current_model_tokens(user_id: int, name_of_model: str) -> None:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
subscription_result = await session.execute(
|
||||||
|
select(Subscription).where(Subscription.user_id == user_id,
|
||||||
|
Subscription.purchased_model == name_of_model)
|
||||||
|
)
|
||||||
|
subscription = subscription_result.scalar()
|
||||||
|
|
||||||
|
if subscription:
|
||||||
|
subscription.purchased_tokens -= 1 if subscription.purchased_tokens > 0 else 0
|
||||||
|
await session.commit()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
print('No user found')
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def remoove_current_model(user_id: int) -> None:
|
||||||
|
async with async_session() as session:
|
||||||
|
async with session.begin():
|
||||||
|
result = await session.execute(select(User).where(User.user_id == user_id))
|
||||||
|
user = result.scalar()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
user.current_model = ""
|
||||||
|
await session.commit()
|
||||||
|
return
|
||||||
|
return
|
35
docker-compose.yml
Normal file
35
docker-compose.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:latest
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy # Ждем, пока база данных станет доступной
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
@ -0,0 +1,40 @@
|
|||||||
|
aiofiles==24.1.0
|
||||||
|
aiogram==3.13.1
|
||||||
|
aiohappyeyeballs==2.4.3
|
||||||
|
aiohttp==3.10.8
|
||||||
|
aiosignal==1.3.1
|
||||||
|
alembic==1.13.3
|
||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.6.0
|
||||||
|
asyncpg==0.29.0
|
||||||
|
attrs==24.2.0
|
||||||
|
certifi==2024.8.30
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
Deprecated==1.2.14
|
||||||
|
distro==1.9.0
|
||||||
|
frozenlist==1.4.1
|
||||||
|
greenlet==3.1.1
|
||||||
|
h11==0.14.0
|
||||||
|
httpcore==1.0.6
|
||||||
|
httpx==0.27.2
|
||||||
|
idna==3.10
|
||||||
|
jiter==0.5.0
|
||||||
|
magic-filter==1.0.12
|
||||||
|
Mako==1.3.5
|
||||||
|
MarkupSafe==3.0.0
|
||||||
|
multidict==6.1.0
|
||||||
|
netaddr==1.3.0
|
||||||
|
openai==1.51.0
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
pydantic==2.9.2
|
||||||
|
pydantic_core==2.23.4
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
requests==2.32.3
|
||||||
|
sniffio==1.3.1
|
||||||
|
SQLAlchemy==2.0.35
|
||||||
|
tqdm==4.66.5
|
||||||
|
typing_extensions==4.12.2
|
||||||
|
urllib3==2.2.3
|
||||||
|
wrapt==1.16.0
|
||||||
|
yarl==1.13.1
|
||||||
|
yookassa==3.3.0
|
Loading…
Reference in New Issue
Block a user