Skip to content

Commit 8ba6e7d

Browse files
committed
Enhance bot functionality with logging, local plan generation, and improved error handling; update command from /start to /plan
1 parent fa68823 commit 8ba6e7d

File tree

5 files changed

+105
-26
lines changed

5 files changed

+105
-26
lines changed

bot.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import logging
2+
import asyncio
3+
import os
24
from aiogram import Bot, Dispatcher
35
from aiogram.enums import ParseMode
46
from aiogram.fsm.storage.memory import MemoryStorage
@@ -7,8 +9,13 @@
79
from config import TOKEN
810
from handlers import start, planner
911

12+
# Создаем необходимые директории
13+
os.makedirs("plans", exist_ok=True)
14+
os.makedirs("fonts", exist_ok=True)
15+
1016
# Logging configuration
1117
logging.basicConfig(level=logging.INFO)
18+
logger = logging.getLogger(__name__)
1219

1320
# Initialize bot and dispatcher
1421
bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
@@ -19,11 +26,12 @@
1926
dp.include_router(planner.router)
2027

2128
if __name__ == "__main__":
22-
import asyncio
23-
24-
2529
async def main():
26-
await dp.start_polling(bot)
30+
try:
31+
logger.info("Starting bot...")
32+
await dp.start_polling(bot)
33+
except Exception as e:
34+
logger.error(f"Error starting bot: {e}")
2735

2836

2937
asyncio.run(main())

handlers/planner.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from services.llm import generate_study_plan
77
from services.pdf import save_plan_to_pdf
8+
from services.txt import save_plan_to_txt
89
from services.db import save_user_plan
910

1011
router = Router()
@@ -19,8 +20,8 @@ def __str__(self):
1920
return "PlanFormat FSM"
2021

2122

22-
@router.message(Command("start"))
23-
async def cmd_start(message: types.Message, state: FSMContext):
23+
@router.message(Command("plan")) # Changed from /start to /plan
24+
async def cmd_plan(message: types.Message, state: FSMContext):
2425
await state.set_state(PlanFormat.waiting_for_format)
2526
await message.answer(
2627
"В каком формате хочешь получить план?\nВыбери: ",
@@ -56,9 +57,10 @@ async def handle_topic(message: types.Message, state: FSMContext):
5657
await message.answer_document(document=types.FSInputFile(pdf_path),
5758
caption="📘 Твой учебный план в PDF")
5859
else:
59-
text_content = "\n".join(plan)
60+
# Using the txt module instead of inline text conversion
61+
txt_path = save_plan_to_txt(plan, message.from_user.id)
6062
await message.answer_document(
61-
document=types.BufferedInputFile(text_content.encode(), filename="study_plan.txt"),
63+
document=types.FSInputFile(txt_path),
6264
caption="📄 Твой учебный план в TXT")
6365

6466
await state.clear()

handlers/start.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from aiogram import Router, types
2+
from aiogram.filters import Command
23

34
router = Router()
45

56

6-
@router.message(lambda msg: msg.text and msg.text.lower() in {"/start", "start"})
7+
@router.message(Command("start"))
78
async def start_handler(message: types.Message):
89
await message.answer(
9-
"👋 Привет! Я бот для создания учебных планов. Напиши, что хочешь изучить.")
10+
"👋 Привет! Я бот для создания учебных планов.\n"
11+
"Используй команду /plan чтобы начать создание плана обучения.")

services/llm.py

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,75 @@
1+
import logging
12
import asyncio
2-
import openai
3-
from openai import RateLimitError, APIError, OpenAIError
3+
from openai import OpenAI, RateLimitError, APIError, OpenAIError
44
from config import OPENAI_API_KEY
55

6-
openai.api_key = OPENAI_API_KEY
6+
# Настройка логгера
7+
logger = logging.getLogger(__name__)
8+
9+
# Initialize client
10+
client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
711

812
# Max retries and delay between retries
913
MAX_RETRIES = 3
10-
RETRY_DELAY = 2 # seconds
14+
RETRY_DELAY = 5 # Увеличиваем задержку между попытками
15+
16+
17+
# Локальный генератор
18+
def generate_local_plan(topic: str) -> list:
19+
"""Generate a basic study plan without using API"""
20+
logger.info(f"Using local plan generator for topic: {topic}")
21+
22+
plan = [
23+
f"Учебный план по теме: {topic}",
24+
"",
25+
"Шаг 1. Изучите основы темы",
26+
f"Шаг 2. Ознакомьтесь с ключевыми концепциями {topic}",
27+
f"Шаг 3. Исследуйте примеры использования {topic}",
28+
"Шаг 4. Выполните практические задания",
29+
"Шаг 5. Закрепите материал с помощью упражнений",
30+
"Шаг 6. Создайте собственный проект",
31+
"",
32+
"Регулярно повторяйте изученный материал"
33+
]
34+
35+
return plan
36+
1137

1238
async def generate_study_plan(topic: str) -> list:
13-
for _ in range(MAX_RETRIES):
39+
"""Generate a study plan using OpenAI API or fallback to local generation"""
40+
# Проверка наличия API ключа
41+
if not OPENAI_API_KEY:
42+
logger.warning("OpenAI API key is missing, using local generator")
43+
return generate_local_plan(topic)
44+
45+
for attempt in range(MAX_RETRIES):
1446
try:
15-
response = openai.chat.completions.create(
47+
logger.info(
48+
f"Generating study plan for topic: {topic} (attempt {attempt + 1}/{MAX_RETRIES})")
49+
50+
response = client.chat.completions.create(
1651
model="gpt-3.5-turbo",
1752
messages=[{"role": "user",
18-
"content": f"Составь подробный учебный план по теме: {topic}"}]
53+
"content": f"Составь подробный учебный план по теме: {topic}. "
54+
f"Раздели план на 5-7 шагов."}]
1955
)
56+
2057
text = response.choices[0].message.content
2158
return text.strip().split("\n")
22-
except RateLimitError:
23-
await asyncio.sleep(RETRY_DELAY)
59+
60+
except RateLimitError as e:
61+
logger.warning(f"Rate limit error: {str(e)}. Retrying in {RETRY_DELAY} seconds...")
62+
await asyncio.sleep(RETRY_DELAY * (attempt + 1)) # Экспоненциальная задержка
63+
2464
except (APIError, OpenAIError) as e:
25-
return [f"Ошибка API: {str(e)}"]
26-
return ["Ошибка: превышен лимит запросов. Попробуйте позже."]
65+
logger.error(f"OpenAI API error: {str(e)}")
66+
logger.info("Falling back to local plan generator")
67+
return generate_local_plan(topic)
68+
69+
except Exception as e:
70+
logger.error(f"Unexpected error: {str(e)}")
71+
return generate_local_plan(topic)
72+
73+
# Если все попытки исчерпаны, используем локальный генератор
74+
logger.warning("All OpenAI API attempts failed, using local generator")
75+
return generate_local_plan(topic)

services/pdf.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
11
import os
22
from fpdf import FPDF
33

4+
45
def save_plan_to_pdf(plan_lines: list[str], user_id: int) -> str:
6+
# Create directories if they don't exist
57
os.makedirs("plans", exist_ok=True)
8+
os.makedirs("fonts", exist_ok=True)
9+
610
filename = f"plans/plan_{user_id}.pdf"
711

12+
# Создаем простой PDF без кастомных шрифтов, которые могут отсутствовать
813
pdf = FPDF()
914
pdf.add_page()
1015

11-
# Добавляем шрифт, который поддерживает кириллицу
12-
pdf.add_font("DejaVu", "", "fonts/DejaVuSans.ttf", uni=True)
13-
pdf.set_font("DejaVu", size=12)
16+
# Use standard font if DejaVu is not available
17+
try:
18+
# Check if font file exists
19+
if os.path.exists("fonts/DejaVuSans.ttf"):
20+
pdf.add_font("DejaVu", "", "fonts/DejaVuSans.ttf", uni=True)
21+
pdf.set_font("DejaVu", size=12)
22+
else:
23+
# Fallback to standard font
24+
pdf.set_font("Arial", size=12)
25+
except Exception:
26+
# If error with custom font, use standard
27+
pdf.set_font("Arial", size=12)
1428

15-
# Добавляем текст из плана
29+
# Convert non-Latin characters to ensure compatibility
1630
for line in plan_lines:
17-
pdf.multi_cell(0, 10, txt=line)
31+
try:
32+
pdf.multi_cell(0, 10, txt=line)
33+
except Exception:
34+
# Handle encoding issues
35+
pdf.multi_cell(0, 10, txt="[Text encoding error]")
1836

1937
pdf.output(filename)
2038
return filename

0 commit comments

Comments
 (0)