Compare commits
5
Commits
1b550e6503
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0bbc739f3 | ||
|
|
42fcc0eb16 | ||
|
|
683f779c31 | ||
|
|
ef71c67683 | ||
|
|
570f0b7ea7 |
+1405
-214
File diff suppressed because it is too large
Load Diff
@@ -42,9 +42,18 @@ class RAGService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
embeddings = self.embedding_service.embed_texts([c.content for c in chunks])
|
EMBEDDING_BATCH_SIZE = 50
|
||||||
|
all_embeddings: list[list[float]] = []
|
||||||
|
|
||||||
|
for i in range(0, len(chunks), EMBEDDING_BATCH_SIZE):
|
||||||
|
batch_chunks = chunks[i:i + EMBEDDING_BATCH_SIZE]
|
||||||
|
batch_texts = [c.content for c in batch_chunks]
|
||||||
|
batch_embeddings = self.embedding_service.embed_texts(batch_texts)
|
||||||
|
all_embeddings.extend(batch_embeddings)
|
||||||
|
|
||||||
|
print(f"Created {len(all_embeddings)} embeddings, upserting to Qdrant...")
|
||||||
await self.vector_repository.upsert_chunks(
|
await self.vector_repository.upsert_chunks(
|
||||||
chunks, embeddings, model_version=self.embedding_service.model_version()
|
chunks, all_embeddings, model_version=self.embedding_service.model_version()
|
||||||
)
|
)
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
|
|||||||
@@ -39,5 +39,10 @@ class TextSplitter:
|
|||||||
|
|
||||||
def _split_sentences(self, text: str) -> Iterable[str]:
|
def _split_sentences(self, text: str) -> Iterable[str]:
|
||||||
parts = re.split(r"(?<=[\.\?\!])\s+", text)
|
parts = re.split(r"(?<=[\.\?\!])\s+", text)
|
||||||
|
if len(parts) == 1 and len(text) > self.chunk_size * 2:
|
||||||
|
chunk_text = []
|
||||||
|
for i in range(0, len(text), self.chunk_size):
|
||||||
|
chunk_text.append(text[i:i + self.chunk_size])
|
||||||
|
return chunk_text
|
||||||
return [p.strip() for p in parts if p.strip()]
|
return [p.strip() for p in parts if p.strip()]
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Use cases для работы с документами
|
|||||||
"""
|
"""
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from typing import BinaryIO, Optional
|
from typing import BinaryIO, Optional
|
||||||
|
import httpx
|
||||||
from src.domain.entities.document import Document
|
from src.domain.entities.document import Document
|
||||||
from src.domain.repositories.document_repository import IDocumentRepository
|
from src.domain.repositories.document_repository import IDocumentRepository
|
||||||
from src.domain.repositories.collection_repository import ICollectionRepository
|
from src.domain.repositories.collection_repository import ICollectionRepository
|
||||||
@@ -10,6 +11,7 @@ from src.domain.repositories.collection_access_repository import ICollectionAcce
|
|||||||
from src.application.services.document_parser_service import DocumentParserService
|
from src.application.services.document_parser_service import DocumentParserService
|
||||||
from src.application.services.rag_service import RAGService
|
from src.application.services.rag_service import RAGService
|
||||||
from src.shared.exceptions import NotFoundError, ForbiddenError
|
from src.shared.exceptions import NotFoundError, ForbiddenError
|
||||||
|
from src.shared.config import settings
|
||||||
|
|
||||||
|
|
||||||
class DocumentUseCases:
|
class DocumentUseCases:
|
||||||
@@ -60,12 +62,34 @@ class DocumentUseCases:
|
|||||||
)
|
)
|
||||||
return await self.document_repository.create(document)
|
return await self.document_repository.create(document)
|
||||||
|
|
||||||
|
async def _send_telegram_notification(self, telegram_id: str, message: str):
|
||||||
|
"""Отправить уведомление пользователю через Telegram Bot API"""
|
||||||
|
if not settings.TELEGRAM_BOT_TOKEN:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"https://api.telegram.org/bot{settings.TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||||
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
json={
|
||||||
|
"chat_id": telegram_id,
|
||||||
|
"text": message,
|
||||||
|
"parse_mode": "HTML"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"Failed to send Telegram notification: {response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending Telegram notification: {e}")
|
||||||
|
|
||||||
async def upload_and_parse_document(
|
async def upload_and_parse_document(
|
||||||
self,
|
self,
|
||||||
collection_id: UUID,
|
collection_id: UUID,
|
||||||
file: BinaryIO,
|
file: BinaryIO,
|
||||||
filename: str,
|
filename: str,
|
||||||
user_id: UUID
|
user_id: UUID,
|
||||||
|
telegram_id: Optional[str] = None
|
||||||
) -> Document:
|
) -> Document:
|
||||||
"""Загрузить и распарсить документ, затем автоматически проиндексировать"""
|
"""Загрузить и распарсить документ, затем автоматически проиндексировать"""
|
||||||
collection = await self.collection_repository.get_by_id(collection_id)
|
collection = await self.collection_repository.get_by_id(collection_id)
|
||||||
@@ -86,11 +110,37 @@ class DocumentUseCases:
|
|||||||
)
|
)
|
||||||
document = await self.document_repository.create(document)
|
document = await self.document_repository.create(document)
|
||||||
|
|
||||||
if self.rag_service:
|
if self.rag_service and telegram_id:
|
||||||
try:
|
try:
|
||||||
await self.rag_service.index_document(document)
|
await self._send_telegram_notification(
|
||||||
|
telegram_id,
|
||||||
|
"🔄 <b>Начинаю индексацию документа...</b>\n\n"
|
||||||
|
f"📄 <b>Документ:</b> {title}\n\n"
|
||||||
|
f"Это может занять некоторое время.\n"
|
||||||
|
f"Вы получите уведомление по завершении."
|
||||||
|
)
|
||||||
|
|
||||||
|
chunks = await self.rag_service.index_document(document)
|
||||||
|
|
||||||
|
await self._send_telegram_notification(
|
||||||
|
telegram_id,
|
||||||
|
"✅ <b>Индексация завершена!</b>\n\n"
|
||||||
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
f"📄 <b>Документ:</b> {title}\n"
|
||||||
|
f"📊 <b>Проиндексировано чанков:</b> {len(chunks)}\n\n"
|
||||||
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
f"💡 <b>Теперь вы можете задавать вопросы по этому документу!</b>\n"
|
||||||
|
f"Просто напишите ваш вопрос, и я найду ответ на основе загруженного документа."
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при автоматической индексации документа {document.document_id}: {e}")
|
print(f"Ошибка при автоматической индексации документа {document.document_id}: {e}")
|
||||||
|
if telegram_id:
|
||||||
|
await self._send_telegram_notification(
|
||||||
|
telegram_id,
|
||||||
|
"⚠️ <b>Ошибка при индексации</b>\n\n"
|
||||||
|
f"Документ загружен, но индексация не завершена.\n"
|
||||||
|
f"Ошибка: {str(e)[:200]}"
|
||||||
|
)
|
||||||
|
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class QdrantVectorRepository(IVectorRepository):
|
|||||||
embeddings: Sequence[list[float]],
|
embeddings: Sequence[list[float]],
|
||||||
model_version: str,
|
model_version: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
BATCH_SIZE = 100
|
||||||
|
|
||||||
points = []
|
points = []
|
||||||
for chunk, vector in zip(chunks, embeddings):
|
for chunk, vector in zip(chunks, embeddings):
|
||||||
points.append(
|
points.append(
|
||||||
@@ -52,6 +54,12 @@ class QdrantVectorRepository(IVectorRepository):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if len(points) >= BATCH_SIZE:
|
||||||
|
self.client.upsert(collection_name=self.collection_name, points=points)
|
||||||
|
points = []
|
||||||
|
|
||||||
|
if points:
|
||||||
self.client.upsert(collection_name=self.collection_name, points=points)
|
self.client.upsert(collection_name=self.collection_name, points=points)
|
||||||
|
|
||||||
async def search(
|
async def search(
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ async def upload_document(
|
|||||||
collection_id=collection_id,
|
collection_id=collection_id,
|
||||||
file=file.file,
|
file=file.file,
|
||||||
filename=file.filename,
|
filename=file.filename,
|
||||||
user_id=current_user.user_id
|
user_id=current_user.user_id,
|
||||||
|
telegram_id=current_user.telegram_id
|
||||||
)
|
)
|
||||||
return DocumentResponse.from_entity(document)
|
return DocumentResponse.from_entity(document)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, C
|
|||||||
from aiogram.filters import Command, StateFilter
|
from aiogram.filters import Command, StateFilter
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from urllib.parse import unquote
|
||||||
from tg_bot.config.settings import settings
|
from tg_bot.config.settings import settings
|
||||||
from tg_bot.infrastructure.http_client import create_http_session
|
from tg_bot.infrastructure.http_client import create_http_session
|
||||||
from tg_bot.infrastructure.telegram.states.collection_states import (
|
from tg_bot.infrastructure.telegram.states.collection_states import (
|
||||||
@@ -10,6 +11,18 @@ from tg_bot.infrastructure.telegram.states.collection_states import (
|
|||||||
CollectionEditStates
|
CollectionEditStates
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_title(title: str) -> str:
|
||||||
|
if not title:
|
||||||
|
return "Без названия"
|
||||||
|
try:
|
||||||
|
decoded = unquote(title)
|
||||||
|
if decoded != title or '%' not in title:
|
||||||
|
return decoded
|
||||||
|
return title
|
||||||
|
except Exception:
|
||||||
|
return title
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
@@ -243,7 +256,7 @@ async def cmd_search(message: Message):
|
|||||||
|
|
||||||
response = f"<b>Результаты поиска:</b> \"{query}\"\n\n"
|
response = f"<b>Результаты поиска:</b> \"{query}\"\n\n"
|
||||||
for i, doc in enumerate(results[:5], 1):
|
for i, doc in enumerate(results[:5], 1):
|
||||||
title = doc.get("title", "Без названия")
|
title = decode_title(doc.get("title", "Без названия"))
|
||||||
content = doc.get("content", "")[:200]
|
content = doc.get("content", "")[:200]
|
||||||
response += f"{i}. <b>{title}</b>\n"
|
response += f"{i}. <b>{title}</b>\n"
|
||||||
response += f" <i>{content}...</i>\n\n"
|
response += f" <i>{content}...</i>\n\n"
|
||||||
@@ -378,7 +391,7 @@ async def show_collection_documents(callback: CallbackQuery):
|
|||||||
|
|
||||||
for i, doc in enumerate(documents[:10], 1):
|
for i, doc in enumerate(documents[:10], 1):
|
||||||
doc_id = doc.get("document_id")
|
doc_id = doc.get("document_id")
|
||||||
title = doc.get("title", "Без названия")
|
title = decode_title(doc.get("title", "Без названия"))
|
||||||
content_preview = doc.get("content", "")[:100]
|
content_preview = doc.get("content", "")[:100]
|
||||||
response += f"{i}. <b>{title}</b>\n"
|
response += f"{i}. <b>{title}</b>\n"
|
||||||
if content_preview:
|
if content_preview:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, C
|
|||||||
from aiogram.filters import StateFilter
|
from aiogram.filters import StateFilter
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from urllib.parse import unquote
|
||||||
from tg_bot.config.settings import settings
|
from tg_bot.config.settings import settings
|
||||||
from tg_bot.infrastructure.http_client import create_http_session
|
from tg_bot.infrastructure.http_client import create_http_session
|
||||||
from tg_bot.infrastructure.telegram.states.collection_states import (
|
from tg_bot.infrastructure.telegram.states.collection_states import (
|
||||||
@@ -13,6 +14,18 @@ from tg_bot.infrastructure.telegram.states.collection_states import (
|
|||||||
DocumentUploadStates
|
DocumentUploadStates
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_title(title: str) -> str:
|
||||||
|
"""Декодирует URL-encoded название документа"""
|
||||||
|
if not title:
|
||||||
|
return "Без названия"
|
||||||
|
try:
|
||||||
|
decoded = unquote(title)
|
||||||
|
if decoded != title or '%' not in title:
|
||||||
|
return decoded
|
||||||
|
return title
|
||||||
|
except Exception:
|
||||||
|
return title
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
@@ -108,7 +121,7 @@ async def view_document(callback: CallbackQuery):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
title = document.get("title", "Без названия")
|
title = decode_title(document.get("title", "Без названия"))
|
||||||
content = document.get("content", "")
|
content = document.get("content", "")
|
||||||
collection_id = document.get("collection_id")
|
collection_id = document.get("collection_id")
|
||||||
|
|
||||||
@@ -184,7 +197,7 @@ async def edit_document_prompt(callback: CallbackQuery, state: FSMContext):
|
|||||||
await callback.message.answer(
|
await callback.message.answer(
|
||||||
"<b>Редактирование документа</b>\n\n"
|
"<b>Редактирование документа</b>\n\n"
|
||||||
"Отправьте новое название документа или /skip чтобы оставить текущее.\n\n"
|
"Отправьте новое название документа или /skip чтобы оставить текущее.\n\n"
|
||||||
f"Текущее название: <b>{document.get('title', 'Без названия')}</b>",
|
f"Текущее название: <b>{decode_title(document.get('title', 'Без названия'))}</b>",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
@@ -360,8 +373,9 @@ async def process_upload_document(message: Message, state: FSMContext):
|
|||||||
|
|
||||||
if result:
|
if result:
|
||||||
await message.answer(
|
await message.answer(
|
||||||
f"<b>Документ загружен</b>\n\n"
|
f"<b>✅ Документ загружен и добавлен в коллекцию</b>\n\n"
|
||||||
f"Название: <b>{result.get('title', filename)}</b>",
|
f"<b>Название:</b> {decode_title(result.get('title', filename))}\n\n"
|
||||||
|
f"📄 Документ сейчас индексируется. Вы получите уведомление, когда индексация завершится.\n\n",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ from aiogram.types import Message
|
|||||||
from tg_bot.config.settings import settings
|
from tg_bot.config.settings import settings
|
||||||
from tg_bot.domain.user_service import UserService, User
|
from tg_bot.domain.user_service import UserService, User
|
||||||
from tg_bot.application.services.rag_service import RAGService
|
from tg_bot.application.services.rag_service import RAGService
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
rag_service = RAGService()
|
rag_service = RAGService()
|
||||||
@@ -60,22 +62,39 @@ async def process_premium_question(message: Message, user: User, question_text:
|
|||||||
|
|
||||||
# Беседа уже сохранена в бэкенде через API /rag/question
|
# Беседа уже сохранена в бэкенде через API /rag/question
|
||||||
|
|
||||||
|
import re
|
||||||
|
formatted_answer = answer
|
||||||
|
formatted_answer = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', formatted_answer)
|
||||||
|
formatted_answer = re.sub(r'^(\d+)\.\s+', r'\1. ', formatted_answer, flags=re.MULTILINE)
|
||||||
|
formatted_answer = formatted_answer.replace("- ", "• ")
|
||||||
|
|
||||||
response = (
|
response = (
|
||||||
f"<b>Ваш вопрос:</b>\n"
|
f"<b>Ваш вопрос:</b>\n"
|
||||||
f"<i>{question_text[:200]}</i>\n\n"
|
f"<i>{question_text[:200]}</i>\n\n"
|
||||||
f"<b>Ответ:</b>\n{answer}\n\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
f"💬 <b>Ответ:</b>\n\n"
|
||||||
|
f"{formatted_answer}\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if sources:
|
if sources:
|
||||||
response += f"<b>Источники:</b>\n"
|
response += f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
response += f"📚 <b>Источники:</b>\n"
|
||||||
for idx, source in enumerate(sources[:5], 1):
|
for idx, source in enumerate(sources[:5], 1):
|
||||||
title = source.get('title', 'Без названия')
|
title = source.get('title', 'Без названия')
|
||||||
|
try:
|
||||||
|
from urllib.parse import unquote
|
||||||
|
decoded = unquote(title)
|
||||||
|
if decoded != title or '%' in title:
|
||||||
|
title = decoded
|
||||||
|
except:
|
||||||
|
pass
|
||||||
response += f" {idx}. {title}\n"
|
response += f" {idx}. {title}\n"
|
||||||
response += "\n<i>Используйте /mycollections для просмотра всех коллекций</i>\n\n"
|
response += "\n<i>💡 Используйте /mycollections для просмотра всех коллекций</i>\n\n"
|
||||||
|
|
||||||
response += (
|
response += (
|
||||||
f"<b>Статус:</b> Premium (вопросов безлимитно)\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
f"<b>Всего вопросов:</b> {user.questions_used}"
|
f"✨ <b>Статус:</b> Premium (вопросов безлимитно)\n"
|
||||||
|
f"📊 <b>Всего вопросов:</b> {user.questions_used}"
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -83,9 +102,12 @@ async def process_premium_question(message: Message, user: User, question_text:
|
|||||||
response = (
|
response = (
|
||||||
f"<b>Ваш вопрос:</b>\n"
|
f"<b>Ваш вопрос:</b>\n"
|
||||||
f"<i>{question_text[:200]}</i>\n\n"
|
f"<i>{question_text[:200]}</i>\n\n"
|
||||||
f"Ошибка при генерации ответа. Попробуйте позже.\n\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
f"<b>Статус:</b> Premium\n"
|
f"❌ <b>Ошибка при генерации ответа.</b>\n"
|
||||||
f"<b>Всего вопросов:</b> {user.questions_used}"
|
f"Попробуйте позже.\n\n"
|
||||||
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
f"✨ <b>Статус:</b> Premium\n"
|
||||||
|
f"📊 <b>Всего вопросов:</b> {user.questions_used}"
|
||||||
)
|
)
|
||||||
|
|
||||||
await message.answer(response, parse_mode="HTML")
|
await message.answer(response, parse_mode="HTML")
|
||||||
@@ -109,40 +131,58 @@ async def process_free_question(message: Message, user: User, question_text: str
|
|||||||
|
|
||||||
# Уже все сохранили через /rag/question
|
# Уже все сохранили через /rag/question
|
||||||
|
|
||||||
|
formatted_answer = answer
|
||||||
|
formatted_answer = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', formatted_answer)
|
||||||
|
formatted_answer = re.sub(r'^(\d+)\.\s+', r'\1. ', formatted_answer, flags=re.MULTILINE)
|
||||||
|
formatted_answer = formatted_answer.replace("- ", "• ")
|
||||||
response = (
|
response = (
|
||||||
f"<b>Ваш вопрос:</b>\n"
|
f"<b>Ваш вопрос:</b>\n"
|
||||||
f"<i>{question_text[:200]}</i>\n\n"
|
f"<i>{question_text[:200]}</i>\n\n"
|
||||||
f"<b>Ответ:</b>\n{answer}\n\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
f"💬 <b>Ответ:</b>\n\n"
|
||||||
|
f"{formatted_answer}\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if sources:
|
if sources:
|
||||||
response += f"<b>Источники:</b>\n"
|
response += f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
|
response += f"📚 <b>Источники:</b>\n"
|
||||||
for idx, source in enumerate(sources[:5], 1):
|
for idx, source in enumerate(sources[:5], 1):
|
||||||
title = source.get('title', 'Без названия')
|
title = source.get('title', 'Без названия')
|
||||||
|
try:
|
||||||
|
from urllib.parse import unquote
|
||||||
|
decoded = unquote(title)
|
||||||
|
if decoded != title or '%' in title:
|
||||||
|
title = decoded
|
||||||
|
except:
|
||||||
|
pass
|
||||||
response += f" {idx}. {title}\n"
|
response += f" {idx}. {title}\n"
|
||||||
response += "\n<i>Используйте /mycollections для просмотра всех коллекций</i>\n\n"
|
response += "\n<i>💡 Используйте /mycollections для просмотра всех коллекций</i>\n\n"
|
||||||
|
|
||||||
response += (
|
response += (
|
||||||
f"<b>Статус:</b> Бесплатный доступ\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
f"<b>Использовано вопросов:</b> {user.questions_used}/{settings.FREE_QUESTIONS_LIMIT}\n"
|
f"📊 <b>Статус:</b> Бесплатный доступ\n"
|
||||||
f"<b>Осталось бесплатных:</b> {remaining}\n\n"
|
f"📈 <b>Использовано вопросов:</b> {user.questions_used}/{settings.FREE_QUESTIONS_LIMIT}\n"
|
||||||
|
f"🎯 <b>Осталось бесплатных:</b> {remaining}\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if remaining <= 3 and remaining > 0:
|
if remaining <= 3 and remaining > 0:
|
||||||
response += f"<i>Осталось мало вопросов! Для продолжения используйте /buy</i>\n\n"
|
response += f"⚠️ <i>Осталось мало вопросов! Для продолжения используйте /buy</i>\n\n"
|
||||||
|
|
||||||
response += f"<i>Для безлимитного доступа: /buy</i>"
|
response += f"💎 <i>Для безлимитного доступа: /buy</i>"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating answer: {e}")
|
print(f"Error generating answer: {e}")
|
||||||
response = (
|
response = (
|
||||||
f"<b>Ваш вопрос:</b>\n"
|
f"<b>Ваш вопрос:</b>\n"
|
||||||
f"<i>{question_text[:200]}</i>\n\n"
|
f"<i>{question_text[:200]}</i>\n\n"
|
||||||
f"Ошибка при генерации ответа. Попробуйте позже.\n\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
f"<b>Статус:</b> Бесплатный доступ\n"
|
f"❌ <b>Ошибка при генерации ответа.</b>\n"
|
||||||
f"<b>Использовано вопросов:</b> {user.questions_used}/{settings.FREE_QUESTIONS_LIMIT}\n"
|
f"Попробуйте позже.\n\n"
|
||||||
f"<b>Осталось бесплатных:</b> {remaining}\n\n"
|
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
|
||||||
f"<i>Для безлимитного доступа: /buy</i>"
|
f"📊 <b>Статус:</b> Бесплатный доступ\n"
|
||||||
|
f"📈 <b>Использовано вопросов:</b> {user.questions_used}/{settings.FREE_QUESTIONS_LIMIT}\n"
|
||||||
|
f"🎯 <b>Осталось бесплатных:</b> {remaining}\n\n"
|
||||||
|
f"💎 <i>Для безлимитного доступа: /buy</i>"
|
||||||
)
|
)
|
||||||
|
|
||||||
await message.answer(response, parse_mode="HTML")
|
await message.answer(response, parse_mode="HTML")
|
||||||
|
|||||||
Reference in New Issue
Block a user