приветик! вот код моего телеграм бота для доступа к нейросетям. исправь то, что кнопка "выбрать чат" не реагирует на нажатия. скинь полную исправленную функцию или функции, разъяснив изменения, но не сильно, и не давая полный изменённый код.
import os
import json
import logging
import re
import html
import asyncio
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from io import BytesIO
from PIL import Image
import aiohttp
import openai
from telegram import (
    Update,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    InputFile,
    ReplyKeyboardMarkup
)
from telegram.constants import ParseMode
from telegram.error import TelegramError
from telegram.ext import (
    ApplicationBuilder,
    CommandHandler,
    MessageHandler,
    ContextTypes,
    filters,
    CallbackQueryHandler,
    Defaults,
)

# Настройка логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

# Определение директории скрипта
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

# Утилиты для работы с JSON
def load_json(file_name, default=None):
    file_path = os.path.join(SCRIPT_DIR, file_name)
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        if default is not None:
            return default
        logger.error(f"Файл {file_name} не найден по пути: {file_path}.")
        exit(1)
    except json.JSONDecodeError as e:
        logger.error(f"Ошибка разбора {file_name}: {e}")
        exit(1)
    except Exception as e:
        logger.error(f"Ошибка при загрузке {file_name}: {e}")
        exit(1)

def save_json(file_name, data):
    file_path = os.path.join(SCRIPT_DIR, file_name)
    try:
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)
    except Exception as e:
        logger.error(f"Ошибка при сохранении {file_name}: {e}")

# Загрузка конфигурационных файлов
config = load_json('config.json', default={
    "limits": {
        "cablyai": {"daily_token_limit": 2000000, "image_daily_limit": 10000, "admin_exempt_threshold": 150000},
        "helix": {"daily_requests": 50, "per_user_daily_requests": 15},
        "meowai": {"total_requests": 960, "per_user_requests": 50},
        "rimunace": {"daily_credits": 500, "per_user_daily_requests": 15}
    },
    "authorized_user_ids": [],
    "models": {},
    "providers": {},
    "system_instruction": "ты - хороший ассистент, готовый ответить на любой вопрос.",
    "user_limits": {}
})
usage_store = load_json('usage.json', default={
    "cablyai_tokens": 0,
    "cablyai_images_generated": 0,
    "helix_requests": 0,
    "meowai_requests": 0,
    "rimunace_credits": 0,
    "users": {}
})
user_mode_store = load_json('user_modes.json', default={})
custom_prompts = load_json('custom_prompts.json', default={})

# Настройка глобальных переменных
AUTHORIZED_USER_IDS = set(map(int, config.get('authorized_user_ids', [])))
model_id_to_info = {info['id']: info for name, info in config.get('models', {}).items() if 'id' in info}
model_id_to_name = {model_id: name for model_id, name in ((info.get('id'), name) for name, info in config.get('models', {}).items() if 'id' in info)}
RESTRICTED_MODELS = ['o1-preview', 'o1-mini', 'openai-o1-preview', 'openai-o1-mini']
AUTHORIZED_MODEL_USERS = {5005816138, 1759650343, 7856892361, 488179251, 1110920711}
SYSTEM_INSTRUCTION_TEXT = config.get('system_instruction', "ты ведёшь себя как хороший ассистент, готовый ответить на любой вопрос.")
MAX_MESSAGE_LENGTH = 4096
MAX_IMAGE_SIZE = 50 * 1024 * 1024  # 50MB

# Структуры для хранения состояния
context_store = {}
user_state = {}
current_provider_store = {}  # Новая структура для хранения текущего провайдера пользователя

# Создание необходимых директорий
os.makedirs(os.path.join(SCRIPT_DIR, 'chats'), exist_ok=True)
os.makedirs(os.path.join(SCRIPT_DIR, 'downloads', 'photos'), exist_ok=True)
os.makedirs(os.path.join(SCRIPT_DIR, 'downloads', 'files'), exist_ok=True)
os.makedirs(os.path.join(SCRIPT_DIR, 'responses'), exist_ok=True)

# Отображение названий провайдеров
provider_display_names = {
    'cablyai': 'CatAI',
    'openai': 'OpenAI',
    'helix': 'Helix',
    'meowai': 'MeowAI',
    'rimunace': 'Rimunace'
}

# Функции для работы с чатами и лимитами
def load_chat_context(user_id, model_id, chat_slot, system_instruction):
    file_path = os.path.join(SCRIPT_DIR, 'chats', f"{user_id}_{model_id}_chat{chat_slot}.json")
    if not os.path.exists(file_path):
        return [{"role": "system", "content": system_instruction}]
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except Exception as e:
        logger.error(f"Ошибка загрузки {file_path}: {e}")
        return [{"role": "system", "content": system_instruction}]

def save_chat_context(user_id, model_id, chat_slot, context):
    try:
        with open(os.path.join(SCRIPT_DIR, 'chats', f"{user_id}_{model_id}_chat{chat_slot}.json"), 'w', encoding='utf-8') as f:
            json.dump(context, f, ensure_ascii=False, indent=4)
    except Exception as e:
        logger.error(f"Ошибка сохранения чата: {e}")

def check_and_increment_limit(user_id, provider, model_id=None):
    """
    Проверяет и увеличивает счетчики лимитов для провайдера и пользователя.
    Возвращает True, если разрешено продолжить, иначе False.
    """
    provider = provider.lower()
    if provider not in config['limits']:
        # Нет лимитов для этого провайдера
        return True

    # Получаем лимиты
    if provider == 'cablyai':
        limit = config['limits']['cablyai']['daily_token_limit']
        admin_exempt_threshold = config['limits']['cablyai']['admin_exempt_threshold']
        used = usage_store.get('cablyai_tokens', 0)
        if user_id not in AUTHORIZED_MODEL_USERS and used >= limit - admin_exempt_threshold:
            # Доступ закрыт для всех, кроме админа
            return False
    elif provider == 'helix':
        limit = config['limits']['helix']['daily_requests']
        used = usage_store.get('helix_requests', 0)
        if used >= limit:
            return False
    elif provider == 'meowai':
        limit = config['limits']['meowai']['total_requests']
        used = usage_store.get('meowai_requests', 0)
        if used >= limit:
            return False
    elif provider == 'rimunace':
        if model_id in ['claude-3.5-sonnet-20241022', 'chatgpt-4o-latest']:
            limit = config['limits']['rimunace']['per_user_daily_requests']
            user_used = usage_store.get('users', {}).get(str(user_id), {}).get('rimunace_requests', 0)
            if user_used >= limit:
                return False
        limit = config['limits']['rimunace']['daily_credits']
        used = usage_store.get('rimunace_credits', 0)
        if used >= limit:
            return False
    else:
        return True  # Для других провайдеров лимиты не изменяются

    # Получаем параметры пользователя
    user_limits = config.get('user_limits', {}).get(str(user_id), {})
    provider_user_limit = user_limits.get(provider, "infinite")
    if provider_user_limit != "infinite":
        if provider == 'cablyai':
            user_used = usage_store.get('users', {}).get(str(user_id), {}).get('cablyai_tokens', 0)
            if user_used >= provider_user_limit:
                return False
        elif provider == 'helix':
            user_used = usage_store.get('users', {}).get(str(user_id), {}).get('helix_requests', 0)
            if user_used >= provider_user_limit:
                return False
        elif provider == 'meowai':
            user_used = usage_store.get('users', {}).get(str(user_id), {}).get('meowai_requests', 0)
            if user_used >= provider_user_limit:
                return False
        elif provider == 'rimunace' and model_id in ['claude-3.5-sonnet-20241022', 'chatgpt-4o-latest']:
            user_used = usage_store.get('users', {}).get(str(user_id), {}).get('rimunace_requests', 0)
            if user_used >= provider_user_limit:
                return False

    # Увеличиваем глобальные лимиты
    if provider == 'cablyai':
        usage_store['cablyai_tokens'] = usage_store.get('cablyai_tokens', 0) + 0  # Учет токенов происходит после запроса
    elif provider == 'helix':
        usage_store['helix_requests'] = usage_store.get('helix_requests', 0) + 1
    elif provider == 'meowai':
        usage_store['meowai_requests'] = usage_store.get('meowai_requests', 0) + 1
    elif provider == 'rimunace':
        if model_id in ['claude-3.5-sonnet-20241022', 'chatgpt-4o-latest']:
            usage_store['rimunace_credits'] = usage_store.get('rimunace_credits', 0) + 5

    # Увеличиваем пользовательские лимиты
    if provider_user_limit != "infinite":
        if provider == 'cablyai':
            usage_store.setdefault('users', {}).setdefault(str(user_id), {}).setdefault('cablyai_tokens', 0)
            usage_store['users'][str(user_id)]['cablyai_tokens'] += 0  # Учет токенов происходит после запроса
        elif provider == 'helix':
            usage_store.setdefault('users', {}).setdefault(str(user_id), {}).setdefault('helix_requests', 0)
            usage_store['users'][str(user_id)]['helix_requests'] += 1
        elif provider == 'meowai':
            usage_store.setdefault('users', {}).setdefault(str(user_id), {}).setdefault('meowai_requests', 0)
            usage_store['users'][str(user_id)]['meowai_requests'] += 1
        elif provider == 'rimunace' and model_id in ['claude-3.5-sonnet-20241022', 'chatgpt-4o-latest']:
            usage_store.setdefault('users', {}).setdefault(str(user_id), {}).setdefault('rimunace_requests', 0)
            usage_store['users'][str(user_id)]['rimunace_requests'] += 1

    save_json('usage.json', usage_store)
    return True

# Вспомогательные функции
def markdown_to_html(text):
    replacements = {
        r'\*\*(.*?)\*\*': r'<b>\1</b>',
        r'\*(.*?)\*': r'<i>\1</i>',
        r'__(.*?)__': r'<u>\1</u>',
        r'~~(.*?)~~': r'
\1
',
        r'#{1,6} (.*?)\n': r'<b>\1</b>\n'
    }
    text = html.escape(text)
    for pattern, repl in replacements.items():
        text = re.sub(pattern, repl, text, flags=re.MULTILINE)
    return text

def split_message(message, max_length):
    return [message[i:i + max_length] for i in range(0, len(message), max_length)]

def get_provider_buttons():
    keyboard = [['CatAI', 'OpenAI'], ['Helix', 'MeowAI'], ['Rimunace']]
    return ReplyKeyboardMarkup(keyboard, resize_keyboard=True, one_time_keyboard=False)

def get_model_buttons_by_provider(provider):
    buttons = []
    if provider == 'rimunace':
        # Первые две модели
        rimunace_primary_models = [
            "rimunace-claude-3.5-sonnet",
            "rimunace-chatgpt-4o-latest"
        ]
        for model_id in rimunace_primary_models:
            model_name = model_id_to_name.get(model_id, model_id)
            buttons.append([InlineKeyboardButton(f"{html.escape(model_name)}", callback_data=f"model_select:{model_id}")])
        # Остальные безлимитные модели
        rimunace_unlimited_models = [
            "rimunace-gpt-4-turbo",
            "llama-3.1-nemotron-70b-instruct",
            "gemini-1.0-pro-002"
        ]
        for model_id in rimunace_unlimited_models:
            model_name = model_id_to_name.get(model_id, model_id)
            buttons.append([InlineKeyboardButton(f"{html.escape(model_name)}", callback_data=f"model_select:{model_id}")])
    elif provider == 'meowai':
        meowai_models = [
            "meowai-chatgpt-4o-latest"
        ]
        for model_id in meowai_models:
            model_name = model_id_to_name.get(model_id, model_id)
            buttons.append([InlineKeyboardButton(f"{html.escape(model_name)}", callback_data=f"model_select:{model_id}")])
    else:
        for name, info in config.get('models', {}).items():
            if info.get('provider', '').lower() == provider.lower() and not name.endswith('(rumi)'):
                buttons.append([InlineKeyboardButton(html.escape(name), callback_data=f"model_select:{info['id']}")])
    # Добавляем кнопку "сменить провайдера" в конце
    buttons.append([InlineKeyboardButton("сменить провайдера", callback_data='change_provider')])
    return InlineKeyboardMarkup(buttons)

def get_chat_slot_buttons(model_id):
    model_info = model_id_to_info.get(model_id, {})
    model_type = model_info.get('type', 'text').lower()
    if model_type == 'image':
        return InlineKeyboardMarkup([
            [InlineKeyboardButton("Единый чат", callback_data=f"chat_slot:1:{model_id}")]
        ])
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("Чат 1", callback_data=f"chat_slot:1:{model_id}"),
         InlineKeyboardButton("Чат 2", callback_data=f"chat_slot:2:{model_id}")],
        [InlineKeyboardButton("далее", callback_data='next_chats')]
    ])

def get_additional_chat_buttons(model_id):
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("Чат 3", callback_data=f'chat_slot:3:{model_id}'),
         InlineKeyboardButton("Чат 4", callback_data=f'chat_slot:4:{model_id}')],
        [InlineKeyboardButton("назад", callback_data='back_chats')]
    ])

def get_main_buttons(user_id, model_id=None):
    buttons = []
    if model_id:
        buttons += [
            [InlineKeyboardButton("сменить модель", callback_data='change_model'),
             InlineKeyboardButton("выбрать чат", callback_data='change_chat_slot'),
             InlineKeyboardButton("очистить контекст", callback_data='clear_context')],
            [InlineKeyboardButton("продолжить ответ", callback_data='continue')]
        ]
    else:
        buttons += [[InlineKeyboardButton("сменить модель", callback_data='change_model')]]
    return InlineKeyboardMarkup(buttons)

def save_md_file(content, user_id):
    filename = os.path.join(SCRIPT_DIR, 'responses', f"{user_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}.md")
    try:
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(content)
        return filename
    except Exception as e:
        logger.error(f"Ошибка сохранения .md файла: {e}")
        return None

def is_image_supported(model_id):
    supported_models = [
        'openai-chatgpt-4o-latest', 'openai-gpt-4-turbo', 'openai-gpt-4o-mini',
        'chatgpt-4o-latest', 'gpt-4-turbo', 'gpt-4o-mini',
        "FLUX.1.1 [pro]", "flux.1.1-pro-ultra", "FLUX 1.1 [pro] ultra raw",
        "sd3.5", "stable-diffusion-3.5-turbo"
    ]
    return model_id in supported_models

def get_api_model_id(model_id, provider):
    prefixes = {
        'openai': 'openai-',
        'helix': 'helix-',
        'meowai': 'meowai-',
        'rimunace': 'rimunace-'
    }
    prefix = prefixes.get(provider, '')
    if prefix and model_id.startswith(prefix):
        return model_id[len(prefix):]
    return model_id
# Асинхронные функции для API запросов
async def send_api_request(provider, model_id, messages=None, prompt=None, model_type='text'):
    provider_info = config['providers'].get(provider, {})
    if model_type in ['text', 'multimodal']:
        api_url = provider_info.get('api_endpoint_text') or provider_info.get('api_endpoint')
    elif model_type == 'image':
        api_url = provider_info.get('api_endpoint_image') or provider_info.get('api_endpoint')
    else:
        logger.error(f"Неизвестный тип модели: {model_type}")
        return None

    if not api_url or not provider_info.get('api_key'):
        logger.error(f"{provider} API настройки отсутствуют в config.json.")
        return None

    headers = {'Authorization': f"Bearer {provider_info['api_key']}", 'Content-Type': 'application/json'}
    data = {"model": get_api_model_id(model_id, provider)}

    if model_type in ['text', 'multimodal']:
        data["messages"] = messages
    elif model_type == 'image':
        data.update({"prompt": prompt, "n": 1, "size": "1024x1024"})
    else:
        logger.error(f"Неизвестный тип модели: {model_type}")
        return None

    logger.info(f"Отправка запроса к {provider} API на URL: {api_url} с данными: {data}")

    try:
        async with aiohttp.ClientSession() as session:
            async with session.post(api_url, headers=headers, json=data) as response:
                if response.status in [200, 201]:
                    response_json = await response.json()
                    logger.info(f"Получен ответ от {provider} API: {response_json}")
                    return response_json
                else:
                    error_text = await response.text()
                    logger.error(f"Ошибка запроса к {provider} API: {response.status} - {error_text}")
                    return None
    except Exception as e:
        logger.error(f"Ошибка при запросе к {provider} API: {e}")
        return None

async def translate_to_english(text, user_id, context_obj):
    if not re.search('[а-яА-Я]', text):
        return text  # если текст уже на английском

    translation_messages = [
        {"role": "system", "content": "you are a helpful assistant that translates russian text to english."},
        {"role": "user", "content": f"переведи следующий текст на английский язык и верни только переведенный текст:\n\n{text}"}
    ]
    response = await send_api_request('cablyai', "chatgpt-4o-latest", messages=translation_messages, model_type='multimodal')
    if response and "choices" in response and response["choices"]:
        # Обработка использования токенов для CablyAI
        if 'usage' in response and 'total_tokens' in response['usage']:
            total_tokens = response['usage']['total_tokens']
            limit = config['limits'].get('cablyai', {}).get('daily_token_limit', 2000000)
            used = usage_store.get('cablyai_tokens', 0)
            if used + total_tokens > limit:
                logger.info("CablyAI daily token limit exceeded during translation.")
                await context_obj.bot.send_message(chat_id=user_id, text="Достигнут дневной лимит токенов для перевода.")
                return None
            usage_store['cablyai_tokens'] = used + total_tokens
            # Увеличиваем персональный лимит пользователя
            usage_store.setdefault('users', {}).setdefault(str(user_id), {}).setdefault('cablyai_tokens', 0)
            user_limit = config.get('user_limits', {}).get(str(user_id), {}).get('cablyai', "infinite")
            if user_limit != "infinite":
                usage_store['users'][str(user_id)]['cablyai_tokens'] += total_tokens
            save_json('usage.json', usage_store)
        return response["choices"][0].get('message', {}).get('content', '').strip()
    logger.error("Ошибка при переводе текста.")
    # Попытка перевода через Helix
    translation_messages_helix = [
        {"role": "system", "content": "you are a helpful assistant that translates russian text to english."},
        {"role": "user", "content": f"переведи следующий текст на английский язык и верни только переведенный текст:\n\n{text}"}
    ]
    response_helix = await send_api_request('helix', "chatgpt-4o-latest", messages=translation_messages_helix, model_type='multimodal')
    if response_helix and "choices" in response_helix and response_helix["choices"]:
        return response_helix["choices"][0].get('message', {}).get('content', '').strip()
    logger.error("Ошибка при переводе текста через Helix.")
    return None

async def generate_image(provider, model_id, prompt, user_id, context_obj):
    # Проверка лимита генерации изображений
    if provider == 'cablyai':
        image_used = usage_store.get('cablyai_images_generated', 0)
        image_limit = config['limits']['cablyai'].get('image_daily_limit', 10000)
        if image_used >= image_limit:
            await context_obj.bot.send_message(chat_id=user_id, text="Достигнут дневной лимит генерации изображений.")
            return None

    # Оповещаем пользователя о задержке
    await context_obj.bot.send_message(
        chat_id=user_id,
        text="Картинка будет генерироваться чуть дольше из-за необходимости перевода текста на английский."
    )
    response = await send_api_request(provider, model_id, prompt=prompt, model_type='image')
    if response and 'data' in response and response['data']:
        image_url = response['data'][0].get('url')
        if provider == 'cablyai':
            usage_store['cablyai_images_generated'] = usage_store.get('cablyai_images_generated', 0) + 1
            save_json('usage.json', usage_store)
        return image_url
    logger.error(f"Ошибка генерации изображения через {provider} API.")
    return None

# Функция для получения ответа модели
async def get_model_response(provider, model_id, messages, model_type, user_id, prompt=None):
    if provider in ['helix', 'meowai']:
        response = await send_api_request(provider, model_id, messages=messages, model_type=model_type)
        if response and isinstance(response, dict):
            if 'choices' in response and response['choices']:
                return response['choices'][0].get('message', {}).get('content', None)
    elif provider == 'cablyai':
        response = await send_api_request(provider, model_id, messages=messages, prompt=prompt, model_type=model_type)
        if model_type == 'image':
            return response.get('data', [{}])[0].get('url', None)
        elif response and "choices" in response and response["choices"]:
            # Обработка использования токенов для CablyAI
            if 'usage' in response and 'total_tokens' in response['usage']:
                total_tokens = response['usage']['total_tokens']
                limit = config['limits'].get('cablyai', {}).get('daily_token_limit', 2000000)
                used = usage_store.get('cablyai_tokens', 0)
                if used + total_tokens > limit:
                    logger.info("CablyAI daily token limit exceeded.")
                    return None
                usage_store['cablyai_tokens'] = used + total_tokens
                # Увеличиваем персональный лимит пользователя
                usage_store.setdefault('users', {}).setdefault(str(user_id), {}).setdefault('cablyai_tokens', 0)
                user_limit = config.get('user_limits', {}).get(str(user_id), {}).get('cablyai', "infinite")
                if user_limit != "infinite":
                    usage_store['users'][str(user_id)]['cablyai_tokens'] += total_tokens
                save_json('usage.json', usage_store)
            return response["choices"][0].get('message', {}).get('content', None)
    elif provider == 'openai':
        api_model = get_api_model_id(model_id, provider)
        try:
            if model_type == 'image' and prompt:
                # Предполагается, что OpenAI обрабатывает образы отдельно
                response = openai.Image.create_edit(image=open(prompt, 'rb'), prompt=prompt, n=1, size="1024x1024")
                return response['data'][0].get('url', None)
            else:
                response = openai.ChatCompletion.create(model=api_model, messages=messages)
                return response['choices'][0]['message']['content']
        except openai.error.OpenAIError as e:
            logger.error(f"Ошибка OpenAI API: {e}")
    elif provider == 'rimunace':
        response = await send_api_request(provider, model_id, messages=messages, model_type=model_type)
        if response and "choices" in response and response["choices"]:
            return response["choices"][0].get('message', {}).get('content', None)
    return None

# Функция handle_continue
async def handle_continue(user_id, context_obj, update, query):
    current_model_id = user_state[user_id].get('current_model_id')
    current_chat_slot = user_state[user_id].get('current_chat_slot')
    if not current_model_id or not current_chat_slot:
        await query.answer(text='Модель или чат не выбраны.', show_alert=True)
        return
    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai').lower()
    model_type = model_info.get('type', 'text').lower()
    # Проверка доступности модели
    if provider == 'cablyai':
        global_tokens = usage_store.get('cablyai_tokens', 0)
        admin_exempt_threshold = config['limits']['cablyai'].get('admin_exempt_threshold', 150000)
        if global_tokens >= config['limits']['cablyai']['daily_token_limit'] - admin_exempt_threshold:
            if user_id not in AUTHORIZED_MODEL_USERS:
                await query.message.reply_text('Доступ к CatAI моделям временно закрыт.')
                return
    elif provider in ['helix', 'meowai', 'rimunace']:
        if not check_and_increment_limit(user_id, provider, current_model_id):
            await query.message.reply_text('Вы достигли лимита запросов для этого провайдера.', reply_markup=get_main_buttons(user_id, current_model_id))
            return
    context_messages = context_store[user_id][current_model_id][current_chat_slot]
    context_messages.append({"role": "user", "content": "Продолжи"})
    answer = await get_model_response(provider, current_model_id, context_messages, model_type, user_id)
    if answer:
        context_messages.append({"role": "assistant", "content": answer})
        save_chat_context(user_id, current_model_id, current_chat_slot, context_messages)
        if model_type != 'image':
            answer_html = markdown_to_html(answer)
            for msg in split_message(answer_html, MAX_MESSAGE_LENGTH):
                await context_obj.bot.send_message(chat_id=user_id, text=msg, reply_markup=get_main_buttons(user_id, current_model_id))
    else:
        await query.answer(text='Не удалось получить ответ от модели или превышен лимит.', show_alert=True)

# Обработчики команд
async def add_user(update: Update, context: ContextTypes.DEFAULT_TYPE):
    admin_id = 5005816138
    if update.effective_user.id != admin_id:
        await update.message.reply_text("У вас нет прав для использования этой команды.")
        return
    if len(context.args) != 1 or not context.args[0].isdigit():
        await update.message.reply_text("Использование: /adduser <telegram_id>")
        return
    new_user_id = int(context.args[0])
    if new_user_id in AUTHORIZED_USER_IDS:
        await update.message.reply_text("Этот пользователь уже авторизован.")
        return
    AUTHORIZED_USER_IDS.add(new_user_id)
    # Установка персональных лимитов по умолчанию (можно настроить отдельно)
    config.setdefault('user_limits', {})[str(new_user_id)] = {
        "cablyai": 500000,
        "helix": 15,
        "meowai": 50,
        "rimunace": 15
    }
    save_json('config.json', config)
    await update.message.reply_text(f"Пользователь {new_user_id} успешно добавлен.")
    try:
        await context.bot.send_message(chat_id=new_user_id, text="Вас добавили в список пользователей, имеющих доступ к боту.")
    except Exception as e:
        logger.error(f"Не удалось отправить сообщение новому пользователю {new_user_id}: {e}")

# Описания моделей
MODEL_DESCRIPTIONS = {
    "claude-3.5-sonnet-20241022": "передовая модель Anthropic. быстрая, в задачах с кодом в чём-то лучше даже OpenAI o1-mini и preview. актуальность знаний: до апреля 2024. контекстное окно: 200000 токенов. выплюнуть за раз может: 5000 символов ( символ не равно токен ).",
    "claude-3.5-haiku": "почти передовая модель Anthropic. совсем новая, быстрая, лучше чем предшественник Claude 3 Haiku. её учили хорошо писать код! актуальность знаний: неизвестна. контекстное окно: 200000 токенов. выплюнуть за раз может: 4096 токенов.",
    "claude-3-opus": "бывшая передовая модель Anthropic. медленная, но очень хороша в генерации текстов и удивляет своими познаниями русского языка. актуальность знаний: до августа 2023. контекстное окно: 200000 токенов. выплюнуть за раз может: 4096 токенов.",
    "searchgpt": "новейшая модель OpenAI. прорыв в поиске информации, в некотором смысле убийца Google. имеет доступ в интернет! старается предоставлять точные данные используя лучшие источники! актуальность данных: онлайн, контекстное окно: неизвестно. выплюнуть за раз может: неизвестно.",
    "chatgpt-4o-latest": "новейшая версия модели GPT-4o за, вроде, сентябрь 2024, созданная OpenAI. быстрая, хороша для широкого спектра задач. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 16384 токена.",
    "gpt-4-turbo": "бывшая передовая модель OpenAI. иногда в чём-то превосходит даже GPT-4o-latest, но уступает в скорости и сильно дороже. актуальность знаний: до декабря 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 4096 токенов.",
    "gpt-4o-mini": "уменьшенная версия GPT-4o. очень быстрая! но по качеству ответов уступает. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 16384 токена.",
    "o1-preview": "передовая модель OpenAI. использует цепочку рассуждений для ответов, что повышает их точность и размер, но генерирует ответ довольно долго относительно других моделей. в точных науках и коде показывает себя лучше любых моделей. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 65536 токенов.",
    "o1-mini": "уменьшенная версия модели o1 от OpenAI. тоже использует цепочку рассуждений, но думает намного быстрее. может уступать o1-preview в качестве ответов, но это компенсируется её на 80% более низкой ценой (для тех кто платит за неё) и быстротой ответов. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 32768 токенов.",
    "llama-3.1-405b-turbo": "текстовая модель. сопоставима по качеству ответов с остальными моделями, но может уступать, и выдавать всякие иероглифы посреди ответа. но почему бы и не добавить? актуальность знаний: до декабря 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: неизвестно.",
    "FLUX.1.1 [pro]": "лучшая существующая модель генерации изображений. некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "flux.1.1-pro-ultra": "как Flux 1.1 Pro, но с х4 большим качеством картинок! некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "FLUX 1.1 [pro] ultra raw": "как Flux 1.1 Pro, но с х4 большим качеством картинок! сделана так чтобы в картинках было меньше синтетики и больше реальности. некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "sd3.5": "модель для генерации изображений по текстовому описанию. контекста не имеет, работая лишь с текущим заданным запросом.",
    "stable-diffusion-3.5-turbo": "более новая и быстрая модель для генерации изображений по текстовому описанию. контекста не имеет, работая лишь с текущим заданным запросом.",
    "helix-chatgpt-4o-latest": "та же chatgpt-4o-latest, что и выше. отличия: поставляется с помощью ключа api Helix.",
    "meowai-chatgpt-4o-latest": "та же chatgpt-4o-latest, что и выше. отличия: поставляется с помощью ключа api MeowAI.",
    "rimunace-gpt-4-turbo": "модель GPT-4 Turbo от Rimunace.",
    "rimunace-llama-3.1-nemotron-70b-instruct": "модель LLaMA 3.1 Nemotron 70B Instruct от Rimunace.",
    "rimunace-gemini-1.0-pro-002": "модель Gemini 1.0 Pro-002 от Rimunace.",
    "openai-chatgpt-4o-latest": "та же chatgpt-4o-latest, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-gpt-4-turbo": "та же GPT-4 Turbo, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-gpt-4o-mini": "та же GPT-4o Mini, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-o1-preview": "та же o1-preview, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-o1-mini": "та же o1-mini, что и выше. отличия: поставляется с помощью ключа api OpenAI."
}

async def models_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if not model_id_to_info:
        await update.message.reply_text('Нет доступных моделей.', reply_markup=get_main_buttons(user_id))
        return

    descriptions = []
    for mid, info in model_id_to_info.items():
        model_name = model_id_to_name.get(mid, 'Неизвестная модель')
        description = MODEL_DESCRIPTIONS.get(mid, 'Описание для этой модели отсутствует.')
        descriptions.append(f"- <b>{html.escape(model_name)}</b>: {html.escape(description)}")

    chunk_size = 10
    for i in range(0, len(descriptions), chunk_size):
        chunk = descriptions[i:i+chunk_size]
        message = "<b>Описание доступных моделей:</b>\n\n" + "\n".join(chunk)
        await update.message.reply_text(message, parse_mode=ParseMode.HTML)

async def setmode(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    if len(context.args) != 1 or context.args[0].lower() not in ['message', 'file']:
        await update.message.reply_text('Использование: /setmode [message|file]')
        return
    mode = context.args[0].lower()
    user_mode_store[str(user_id)] = mode
    save_json('user_modes.json', user_mode_store)
    await update.message.reply_text(f'Режим получения ответов установлен на: <b>{mode}</b>', parse_mode=ParseMode.HTML)

async def mode_command_func(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return
    mode = user_mode_store.get(str(user_id), 'message')
    await update.message.reply_text(f'Текущий режим получения ответов: <b>{mode}</b>', parse_mode=ParseMode.HTML)

async def limits_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text("у тебя нет доступа к этому боту.")
        return
    
    user_limits = config.get('user_limits', {}).get(str(user_id), {})
    global_limits = config.get('limits', {})
    lines = ["<b>Ваши лимиты:</b>"]

    # CatAI
    cablyai_used = usage_store.get('cablyai_tokens', 0)
    cablyai_limit = global_limits.get('cablyai', {}).get('daily_token_limit', 2000000)
    cablyai_remaining = max(cablyai_limit - cablyai_used, 0)
    cablyai_image_used = usage_store.get('cablyai_images_generated', 0)
    cablyai_image_limit = global_limits.get('cablyai', {}).get('image_daily_limit', 10000)
    cablyai_image_remaining = max(cablyai_image_limit - cablyai_image_used, 0)
    cablyai_user_limit = user_limits.get('cablyai', "infinite")
    cablyai_user_used = usage_store.get('users', {}).get(str(user_id), {}).get('cablyai_tokens', 0)
    
    if cablyai_user_limit == "infinite":
        cablyai_user_display = "∞"
        cablyai_remaining_user = "∞"
    else:
        cablyai_user_display = f"{cablyai_user_limit}"
        cablyai_remaining_user = f"{max(cablyai_user_limit - cablyai_user_used, 0)}"
    
    lines.append(f"\n- <b>CatAI</b>: Использовано токенов: {cablyai_used} / {cablyai_limit} (Осталось: {cablyai_remaining})")
    lines.append(f"  Ваш лимит токенов: {cablyai_user_used} / {cablyai_user_display} (Осталось: {cablyai_remaining_user})")
    lines.append(f"  Использовано изображений: {cablyai_image_used} / {cablyai_image_limit} (Осталось: {cablyai_image_remaining})")

    # Helix
    helix_used = usage_store.get('helix_requests', 0)
    helix_limit = global_limits.get('helix', {}).get('daily_requests', 50)
    helix_remaining = max(helix_limit - helix_used, 0)
    helix_user_limit = user_limits.get('helix', "infinite")
    helix_user_used = usage_store.get('users', {}).get(str(user_id), {}).get('helix_requests', 0)
    
    if helix_user_limit == "infinite":
        helix_user_display = "∞"
        helix_remaining_user = "∞"
    else:
        helix_user_display = f"{helix_user_limit}"
        helix_remaining_user = f"{max(helix_user_limit - helix_user_used, 0)}"
    
    lines.append(f"\n- <b>Helix</b>: Использовано запросов: {helix_used} / {helix_limit} (Осталось: {helix_remaining})")
    lines.append(f"  Ваш лимит: {helix_user_used} / {helix_user_display} (Осталось: {helix_remaining_user})")

    # MeowAI
    meowai_used = usage_store.get('meowai_requests', 0)
    meowai_limit = global_limits.get('meowai', {}).get('total_requests', 960)
    meowai_remaining = max(meowai_limit - meowai_used, 0)
    meowai_user_limit = user_limits.get('meowai', "infinite")
    meowai_user_used = usage_store.get('users', {}).get(str(user_id), {}).get('meowai_requests', 0)
    
    if meowai_user_limit == "infinite":
        meowai_user_display = "∞"
        meowai_remaining_user = "∞"
    else:
        meowai_user_display = f"{meowai_user_limit}"
        meowai_remaining_user = f"{max(meowai_user_limit - meowai_user_used, 0)}"
    
    lines.append(f"\n- <b>MeowAI</b>: Использовано запросов: {meowai_used} / {meowai_limit} (Осталось: {meowai_remaining})")
    lines.append(f"  Ваш лимит: {meowai_user_used} / {meowai_user_display} (Осталось: {meowai_remaining_user})")

    # Rimunace
    rimunace_used = usage_store.get('rimunace_credits', 0)
    rimunace_limit = global_limits.get('rimunace', {}).get('daily_credits', 500)
    rimunace_remaining = max(rimunace_limit - rimunace_used, 0)
    rimunace_user_limit = user_limits.get('rimunace', "infinite")
    rimunace_user_used = usage_store.get('users', {}).get(str(user_id), {}).get('rimunace_requests', 0)
    
    if rimunace_user_limit == "infinite":
        rimunace_user_display = "∞"
        rimunace_remaining_user = "∞"
    else:
        rimunace_user_display = f"{rimunace_user_limit}"
        rimunace_remaining_user = f"{max(rimunace_user_limit - rimunace_user_used, 0)}"
    
    lines.append(f"\n- <b>Rimunace</b>: Использовано кредитов: {rimunace_used} / {rimunace_limit} (Осталось: {rimunace_remaining})")
    lines.append(f"  Ваш лимит запросов: {rimunace_user_used} / {rimunace_user_limit} (Осталось: {rimunace_remaining_user})")

    reset_time = config.get('limit_reset_time', '02:00')
    lines.append(f"\n<code>Лимиты сбросятся в {reset_time} по московскому времени.</code>")
    await update.message.reply_text("\n".join(lines), parse_mode=ParseMode.HTML)
    
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    user_state[user_id] = {'current_model_id': None, 'current_chat_slot': None, 'current_provider': None}
    current_provider_store[user_id] = None  # Инициализация текущего провайдера
    await update.message.reply_text('Привет! Выбери провайдера моделей:', reply_markup=get_provider_buttons())

async def reload_bot(update: Update, context: ContextTypes.DEFAULT_TYPE):
    admin_ids = {5005816138}
    user_id = update.effective_user.id
    if user_id not in admin_ids:
        await update.message.reply_text('Ты не админ и тебе такое нельзя.')
        return
    await update.message.reply_text('Перезагружаю конфигурацию бота...')
    try:
        global config, model_id_to_info, model_id_to_name, AUTHORIZED_USER_IDS, RESTRICTED_MODELS, AUTHORIZED_MODEL_USERS, SYSTEM_INSTRUCTION_TEXT, usage_store, user_mode_store, custom_prompts
        config = load_json('config.json', default=config)
        AUTHORIZED_USER_IDS = set(map(int, config.get('authorized_user_ids', [])))
        model_id_to_info = {info['id']: info for name, info in config.get('models', {}).items() if 'id' in info}
        model_id_to_name = {info['id']: name for name, info in config.get('models', {}).items() if 'id' in info}
        RESTRICTED_MODELS = ['o1-preview', 'o1-mini', 'openai-o1-preview', 'openai-o1-mini']
        AUTHORIZED_MODEL_USERS = {5005816138, 1759650343, 7856892361, 488179251}  # Обновите при необходимости
        SYSTEM_INSTRUCTION_TEXT = config.get('system_instruction', "ты ведёшь себя как хороший ассистент, готовый ответить на любой вопрос.")
        usage_store = load_json('usage.json', default=usage_store)
        user_mode_store = load_json('user_modes.json', default=user_mode_store)
        custom_prompts = load_json('custom_prompts.json', default=custom_prompts)
        save_json('config.json', config)
        save_json('usage.json', usage_store)
        save_json('user_modes.json', user_mode_store)
        save_json('custom_prompts.json', custom_prompts)
        await update.message.reply_text('Конфигурация успешно перезагружена.')
        logger.info("Конфигурация бота успешно перезагружена.")
    except Exception as e:
        logger.error(f"Ошибка при перезагрузке конфигурации: {e}")
        await update.message.reply_text('Произошла ошибка при перезагрузке конфигурации.')

# Команды /custom и /clearcustom
async def custom_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    if len(context.args) < 1:
        await update.message.reply_text('Использование: /custom <твой системный промпт>')
        return
    custom_prompt = ' '.join(context.args)
    custom_prompts[str(user_id)] = custom_prompt
    save_json('custom_prompts.json', custom_prompts)
    await update.message.reply_text('Твой системный промпт успешно сохранён.', parse_mode=ParseMode.HTML)

async def clearcustom_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    if str(user_id) in custom_prompts:
        del custom_prompts[str(user_id)]
        save_json('custom_prompts.json', custom_prompts)
        await update.message.reply_text('Твой пользовательский системный промпт очищен.', parse_mode=ParseMode.HTML)
    else:
        await update.message.reply_text('У тебя нет установленного пользовательского системного промпта.', parse_mode=ParseMode.HTML)

# Описания моделей
MODEL_DESCRIPTIONS = {
    "claude-3.5-sonnet-20241022": "передовая модель Anthropic. быстрая, в задачах с кодом в чём-то лучше даже OpenAI o1-mini и preview. актуальность знаний: до апреля 2024. контекстное окно: 200000 токенов. выплюнуть за раз может: 5000 символов ( символ не равно токен ).",
    "claude-3.5-haiku": "почти передовая модель Anthropic. совсем новая, быстрая, лучше чем предшественник Claude 3 Haiku. её учили хорошо писать код! актуальность знаний: неизвестна. контекстное окно: 200000 токенов. выплюнуть за раз может: 4096 токенов.",
    "claude-3-opus": "бывшая передовая модель Anthropic. медленная, но очень хороша в генерации текстов и удивляет своими познаниями русского языка. актуальность знаний: до августа 2023. контекстное окно: 200000 токенов. выплюнуть за раз может: 4096 токенов.",
    "searchgpt": "новейшая модель OpenAI. прорыв в поиске информации, в некотором смысле убийца Google. имеет доступ в интернет! старается предоставлять точные данные используя лучшие источники! актуальность данных: онлайн, контекстное окно: неизвестно. выплюнуть за раз может: неизвестно.",
    "chatgpt-4o-latest": "новейшая версия модели GPT-4o за, вроде, сентябрь 2024, созданная OpenAI. быстрая, хороша для широкого спектра задач. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 16384 токена.",
    "gpt-4-turbo": "бывшая передовая модель OpenAI. иногда в чём-то превосходит даже GPT-4o-latest, но уступает в скорости и сильно дороже. актуальность знаний: до декабря 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 4096 токенов.",
    "gpt-4o-mini": "уменьшенная версия GPT-4o. очень быстрая! но по качеству ответов уступает. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 16384 токена.",
    "o1-preview": "передовая модель OpenAI. использует цепочку рассуждений для ответов, что повышает их точность и размер, но генерирует ответ довольно долго относительно других моделей. в точных науках и коде показывает себя лучше любых моделей. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 65536 токенов.",
    "o1-mini": "уменьшенная версия модели o1 от OpenAI. тоже использует цепочку рассуждений, но думает намного быстрее. может уступать o1-preview в качестве ответов, но это компенсируется её на 80% более низкой ценой (для тех кто платит за неё) и быстротой ответов. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 32768 токенов.",
    "llama-3.1-405b-turbo": "текстовая модель. сопоставима по качеству ответов с остальными моделями, но может уступать, и выдавать всякие иероглифы посреди ответа. но почему бы и не добавить? актуальность знаний: до декабря 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: неизвестно.",
    "FLUX.1.1 [pro]": "лучшая существующая модель генерации изображений. некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "flux.1.1-pro-ultra": "как Flux 1.1 Pro, но с х4 большим качеством картинок! некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "FLUX 1.1 [pro] ultra raw": "как Flux 1.1 Pro, но с х4 большим качеством картинок! сделана так чтобы в картинках было меньше синтетики и больше реальности. некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "sd3.5": "модель для генерации изображений по текстовому описанию. контекста не имеет, работая лишь с текущим заданным запросом.",
    "stable-diffusion-3.5-turbo": "более новая и быстрая модель для генерации изображений по текстовому описанию. контекста не имеет, работая лишь с текущим заданным запросом.",
    "helix-chatgpt-4o-latest": "та же chatgpt-4o-latest, что и выше. отличия: поставляется с помощью ключа api Helix.",
    "meowai-chatgpt-4o-latest": "та же chatgpt-4o-latest, что и выше. отличия: поставляется с помощью ключа api MeowAI.",
    "rimunace-gpt-4-turbo": "модель GPT-4 Turbo от Rimunace.",
    "rimunace-llama-3.1-nemotron-70b-instruct": "модель LLaMA 3.1 Nemotron 70B Instruct от Rimunace.",
    "rimunace-gemini-1.0-pro-002": "модель Gemini 1.0 Pro-002 от Rimunace.",
    "openai-chatgpt-4o-latest": "та же chatgpt-4o-latest, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-gpt-4-turbo": "та же GPT-4 Turbo, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-gpt-4o-mini": "та же GPT-4o Mini, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-o1-preview": "та же o1-preview, что и выше. отличия: поставляется с помощью ключа api OpenAI.",
    "openai-o1-mini": "та же o1-mini, что и выше. отличия: поставляется с помощью ключа api OpenAI."
}

async def models_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if not model_id_to_info:
        await update.message.reply_text('Нет доступных моделей.', reply_markup=get_main_buttons(user_id))
        return

    descriptions = []
    for mid, info in model_id_to_info.items():
        model_name = model_id_to_name.get(mid, 'Неизвестная модель')
        description = MODEL_DESCRIPTIONS.get(mid, 'Описание для этой модели отсутствует.')
        descriptions.append(f"- <b>{html.escape(model_name)}</b>: {html.escape(description)}")

    chunk_size = 10
    for i in range(0, len(descriptions), chunk_size):
        chunk = descriptions[i:i+chunk_size]
        message = "<b>Описание доступных моделей:</b>\n\n" + "\n".join(chunk)
        await update.message.reply_text(message, parse_mode=ParseMode.HTML)

async def setmode(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    if len(context.args) != 1 or context.args[0].lower() not in ['message', 'file']:
        await update.message.reply_text('Использование: /setmode [message|file]')
        return
    mode = context.args[0].lower()
    user_mode_store[str(user_id)] = mode
    save_json('user_modes.json', user_mode_store)
    await update.message.reply_text(f'Режим получения ответов установлен на: <b>{mode}</b>', parse_mode=ParseMode.HTML)

async def mode_command_func(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return
    mode = user_mode_store.get(str(user_id), 'message')
    await update.message.reply_text(f'Текущий режим получения ответов: <b>{mode}</b>', parse_mode=ParseMode.HTML)

async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    user_state[user_id] = {'current_model_id': None, 'current_chat_slot': None, 'current_provider': None}
    current_provider_store[user_id] = None  # Инициализация текущего провайдера
    await update.message.reply_text('Привет! Выбери провайдера моделей:', reply_markup=get_provider_buttons())

async def reload_bot(update: Update, context: ContextTypes.DEFAULT_TYPE):
    admin_ids = {5005816138}
    user_id = update.effective_user.id
    if user_id not in admin_ids:
        await update.message.reply_text('Ты не админ и тебе такое нельзя.')
        return
    await update.message.reply_text('Перезагружаю конфигурацию бота...')
    try:
        global config, model_id_to_info, model_id_to_name, AUTHORIZED_USER_IDS, RESTRICTED_MODELS, AUTHORIZED_MODEL_USERS, SYSTEM_INSTRUCTION_TEXT, usage_store, user_mode_store, custom_prompts
        config = load_json('config.json', default=config)
        AUTHORIZED_USER_IDS = set(map(int, config.get('authorized_user_ids', [])))
        model_id_to_info = {info['id']: info for name, info in config.get('models', {}).items() if 'id' in info}
        model_id_to_name = {info['id']: name for name, info in config.get('models', {}).items() if 'id' in info}
        RESTRICTED_MODELS = ['o1-preview', 'o1-mini', 'openai-o1-preview', 'openai-o1-mini']
        AUTHORIZED_MODEL_USERS = {5005816138, 1759650343, 7856892361, 488179251, 1110920711}
        SYSTEM_INSTRUCTION_TEXT = config.get('system_instruction', "ты ведёшь себя как хороший ассистент, готовый ответить на любой вопрос.")
        usage_store = load_json('usage.json', default=usage_store)
        user_mode_store = load_json('user_modes.json', default=user_mode_store)
        custom_prompts = load_json('custom_prompts.json', default=custom_prompts)
        save_json('config.json', config)
        save_json('usage.json', usage_store)
        save_json('user_modes.json', user_mode_store)
        save_json('custom_prompts.json', custom_prompts)
        await update.message.reply_text('Конфигурация успешно перезагружена.')
        logger.info("Конфигурация бота успешно перезагружена.")
    except Exception as e:
        logger.error(f"Ошибка при перезагрузке конфигурации: {e}")
        await update.message.reply_text('Произошла ошибка при перезагрузке конфигурации.')

# Обработчики CallbackQuery
async def handle_callback_query(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    user_id = query.from_user.id

    if user_id not in AUTHORIZED_USER_IDS:
        await query.edit_message_text('Тебе сюда мяукать нельзя.')
        return

    data = query.data
    model_id = user_state.get(user_id, {}).get('current_model_id')

    if data.startswith("model_select:"):
        model_id = data.split("model_select:")[1]
        model_info = model_id_to_info.get(model_id, {})
        provider = model_info.get('provider', 'cablyai').lower()
        # Проверка доступности модели
        if provider == 'cablyai':
            global_tokens = usage_store.get('cablyai_tokens', 0)
            admin_exempt_threshold = config['limits']['cablyai'].get('admin_exempt_threshold', 150000)
            if global_tokens >= config['limits']['cablyai']['daily_token_limit'] - admin_exempt_threshold:
                if user_id not in AUTHORIZED_MODEL_USERS:
                    await query.message.reply_text('Доступ к CatAI моделям временно закрыт.')
                    return
        elif provider in ['helix', 'meowai', 'rimunace']:
            if not check_and_increment_limit(user_id, provider, model_id):
                await query.message.reply_text('Вы достигли лимита запросов для этого провайдера.', reply_markup=get_main_buttons(user_id, model_id))
                return
        user_state[user_id]['current_model_id'] = model_id
        user_state[user_id]['current_provider'] = provider
        await context.bot.send_message(
            chat_id=user_id,
            text=f'Выбрана модель: <b>{html.escape(model_id_to_name.get(model_id, "Неизвестная модель"))}</b>\n\nВыберите слот чата:',
            parse_mode=ParseMode.HTML,
            reply_markup=get_chat_slot_buttons(model_id)
        )

    elif data.startswith("chat_slot:"):
        parts = data.split(":")
        if len(parts) == 3:
            _, slot, selected_model_id = parts
            slot = int(slot)
            model_info = model_id_to_info.get(selected_model_id, {})
            provider = model_info.get('provider', 'cablyai').lower()
            user_state[user_id].update({'current_chat_slot': slot, 'current_provider': provider})
            system_instruction = SYSTEM_INSTRUCTION_TEXT
            custom_prompt = custom_prompts.get(str(user_id), "")
            if custom_prompt and selected_model_id not in RESTRICTED_MODELS and model_info.get('type', 'text').lower() != 'image':
                system_instruction = custom_prompt
            context_store.setdefault(user_id, {}).setdefault(selected_model_id, {}).setdefault(slot, load_chat_context(user_id, selected_model_id, slot, system_instruction))
            await context.bot.send_message(
                chat_id=user_id,
                text=f'Выбран <b>чат {slot}</b> для модели <b>{html.escape(model_id_to_name.get(selected_model_id, "Неизвестная модель"))}</b>',
                parse_mode=ParseMode.HTML,
                reply_markup=get_main_buttons(user_id, selected_model_id)
            )
        else:
            # Обработка некорректных данных
            await query.message.reply_text('Некорректный формат данных для выбора чата.')

    elif data == 'change_model':
        current_model_id = user_state[user_id].get('current_model_id')
        current_provider = user_state[user_id].get('current_provider')
        if not current_provider:
            await query.message.reply_text('Провайдер не установлен. Используй /start или "сменить провайдера".')
            return
        user_state[user_id].update({'current_model_id': None, 'current_chat_slot': None})
        await context.bot.send_message(
            chat_id=user_id,
            text='Выбери модель этого провайдера:',
            parse_mode=ParseMode.HTML,
            reply_markup=get_model_buttons_by_provider(current_provider)
        )

    elif data == 'change_provider':
        user_state[user_id].update({'current_model_id': None, 'current_chat_slot': None, 'current_provider': None})
        current_provider_store[user_id] = None
        await context.bot.send_message(
            chat_id=user_id,
            text='Выбери нового провайдера для взаимодействия:',
            parse_mode=ParseMode.HTML,
            reply_markup=get_provider_buttons()
        )

    elif data == 'clear_context':
        current_model_id = user_state[user_id].get('current_model_id')
        current_chat_slot = user_state[user_id].get('current_chat_slot')
        if not current_model_id or not current_chat_slot:
            await query.message.reply_text('Модель или чат не выбраны.')
            return
        model_info = model_id_to_info.get(current_model_id, {})
        provider = model_info.get('provider', 'cablyai').lower()
        system_instruction = SYSTEM_INSTRUCTION_TEXT
        custom_prompt = custom_prompts.get(str(user_id), "")
        if custom_prompt and current_model_id not in RESTRICTED_MODELS and model_info.get('type', 'text').lower() != 'image':
            system_instruction = custom_prompt
        context_store[user_id][current_model_id][current_chat_slot] = [{"role": "system", "content": system_instruction}]
        save_chat_context(user_id, current_model_id, current_chat_slot, context_store[user_id][current_model_id][current_chat_slot])
        await context.bot.send_message(
            chat_id=user_id,
            text=f'Контекст <b>Чат {current_chat_slot}</b> для модели <b>{html.escape(model_id_to_name.get(current_model_id, "Неизвестная модель"))}</b> очищен.',
            parse_mode=ParseMode.HTML,
            reply_markup=get_main_buttons(user_id, current_model_id)
        )

    elif data == 'continue':
        await handle_continue(user_id, context, update, query)

    elif data == 'next_chats':
        current_model_id = user_state[user_id].get('current_model_id')
        if not current_model_id:
            await query.message.reply_text('Модель не выбрана.')
            return
        await query.message.edit_reply_markup(reply_markup=get_additional_chat_buttons(current_model_id))

    elif data == 'back_chats':
        current_model_id = user_state[user_id].get('current_model_id')
        if not current_model_id:
            await query.message.reply_text('Модель не выбрана.')
            return
        await query.message.edit_reply_markup(reply_markup=get_chat_slot_buttons(current_model_id))

    else:
        await query.message.reply_text('Неизвестная команда.')

async def handle_provider_selection(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_text = update.message.text.lower()
    internal_provider = {'catai': 'cablyai', 'openai': 'openai', 'helix': 'helix', 'meowai': 'meowai', 'rimunace': 'rimunace'}.get(user_text)
    if not internal_provider:
        await update.message.reply_text("Некорректный провайдер.")
        return
    user_id = update.effective_user.id
    user_state[user_id].update({'current_provider': internal_provider, 'current_model_id': None, 'current_chat_slot': None})
    current_provider_store[user_id] = internal_provider
    await update.message.reply_text(
        f'Выбран провайдер: <b>{html.escape(provider_display_names.get(internal_provider, user_text.capitalize()))}</b>\n\nВыберите модель:',
        parse_mode=ParseMode.HTML,
        reply_markup=get_model_buttons_by_provider(internal_provider)
    )

# Обработчики сообщений
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    photo = update.message.photo[-1]
    file = await photo.get_file()
    file_path = os.path.join(SCRIPT_DIR, 'downloads', 'photos', f"{user_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}.jpg")
    try:
        await file.download_to_drive(file_path)
    except Exception as e:
        logger.error(f"Ошибка скачивания фото: {e}")
        await update.message.reply_text('Не удалось скачать изображение.')
        return
    user_caption = update.message.caption or ""
    if user_caption:
        # Оповещаем пользователя о задержке
        await update.message.reply_text("Картинка будет генерироваться чуть дольше из-за необходимости перевода текста на английский.")
        translated_text = await translate_to_english(user_caption, user_id, context)
        if translated_text:
            user_caption = translated_text
        else:
            await update.message.reply_text("Не удалось перевести текст.")
            return
    current_model_id = user_state[user_id].get('current_model_id')
    current_chat_slot = user_state[user_id].get('current_chat_slot')
    if not current_model_id or not current_chat_slot:
        await update.message.reply_text('Модель или чат не выбраны.', reply_markup=get_main_buttons(user_id))
        return
    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai').lower()
    if not is_image_supported(current_model_id):
        await update.message.reply_text('Выбранная модель не поддерживает обработку изображений.', reply_markup=get_main_buttons(user_id, current_model_id))
        return
    model_type = model_info.get('type', 'text').lower()
    # Проверка лимитов для провайдеров (кроме cablyai, где лимит учитывается отдельно)
    if provider in ['helix', 'meowai', 'rimunace']:
        if not check_and_increment_limit(user_id, provider, current_model_id):
            await update.message.reply_text('Вы достигли лимита запросов для этого провайдера.', reply_markup=get_main_buttons(user_id, current_model_id))
            return
    # Проверка лимита генерации изображений для cablyai
    if provider == 'cablyai':
        image_used = usage_store.get('cablyai_images_generated', 0)
        image_limit = config['limits']['cablyai'].get('image_daily_limit', 10000)
        if image_used >= image_limit:
            await update.message.reply_text("Достигнут дневной лимит генерации изображений.", reply_markup=get_main_buttons(user_id, current_model_id))
            return
    image_url = await generate_image(provider, current_model_id, user_caption, user_id, context)
    if image_url:
        try:
            await update.message.reply_photo(photo=image_url, caption="твоя картинка:", reply_markup=get_main_buttons(user_id, current_model_id))
        except TelegramError as e:
            logger.error(f"Ошибка отправки фото: {e}")
            await update.message.reply_text("Не удалось отправить изображение.")
    else:
        await update.message.reply_text("Не удалось сгенерировать изображение.")
    if os.path.exists(file_path):
        os.remove(file_path)

async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text('Тебе сюда мяукать нельзя.')
        return
    document = update.message.document
    filename = document.file_name.lower()
    if not filename.endswith(('.py', '.json')):
        await update.message.reply_text('Можно отправлять только .py или .json файлы.')
        return
    file_path = os.path.join(SCRIPT_DIR, 'downloads', 'files', document.file_name)
    try:
        await document.get_file().download_to_drive(file_path)
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        os.remove(file_path)
    except Exception as e:
        logger.error(f"Ошибка обработки файла: {e}")
        await update.message.reply_text('Не удалось обработать файл.')
        return
    current_model_id = user_state[user_id].get('current_model_id')
    current_chat_slot = user_state[user_id].get('current_chat_slot')
    if not current_model_id or not current_chat_slot:
        await update.message.reply_text('Модель или чат не выбраны.', reply_markup=get_main_buttons(user_id))
        return
    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai').lower()
    model_type = model_info.get('type', 'text').lower()
    if model_type != 'multimodal' and model_type != 'text':
        await update.message.reply_text('Модель не поддерживает обработку текстовых файлов.', reply_markup=get_main_buttons(user_id, current_model_id))
        return
    # Проверка лимитов для провайдеров (кроме cablyai, где лимит учитывается после запроса)
    if provider in ['helix', 'meowai', 'rimunace']:
        if not check_and_increment_limit(user_id, provider, current_model_id):
            await update.message.reply_text('Вы достигли лимита запросов для этого провайдера.', reply_markup=get_main_buttons(user_id, current_model_id))
            return
    context_store.setdefault(user_id, {}).setdefault(current_model_id, {}).setdefault(current_chat_slot, load_chat_context(user_id, current_model_id, current_chat_slot, SYSTEM_INSTRUCTION_TEXT))
    context_store[user_id][current_model_id][current_chat_slot].append({"role": "user", "content": content})
    save_chat_context(user_id, current_model_id, current_chat_slot, context_store[user_id][current_model_id][current_chat_slot])
    answer = await get_model_response(provider, current_model_id, context_store[user_id][current_model_id][current_chat_slot], model_type, user_id)
    if answer:
        context_store[user_id][current_model_id][current_chat_slot].append({"role": "assistant", "content": answer})
        save_chat_context(user_id, current_model_id, current_chat_slot, context_store[user_id][current_model_id][current_chat_slot])
        mode = user_mode_store.get(str(user_id), 'message')
        if mode == 'message':
            answer_html = markdown_to_html(answer)
            for msg in split_message(answer_html, MAX_MESSAGE_LENGTH):
                await update.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
        elif mode == 'file':
            file_path_generated = save_md_file(answer, user_id)
            if file_path_generated:
                try:
                    with open(file_path_generated, 'rb') as f:
                        await update.message.reply_document(
                            document=InputFile(f),
                            filename=os.path.basename(file_path_generated),
                            caption="Твой ответ модели в формате markdown.",
                            reply_markup=get_main_buttons(user_id, current_model_id)
                        )
                    os.remove(file_path_generated)
                except Exception as e:
                    logger.error(f"Ошибка отправки файла: {e}")
                    await update.message.reply_text('Не удалось отправить файл.')
            else:
                await update.message.reply_text('Не удалось создать файл с ответом.')
    else:
        if provider == 'cablyai':
            await update.message.reply_text('Не удалось получить ответ от модели или превышен лимит токенов.', reply_markup=get_main_buttons(user_id, current_model_id))
        else:
            await update.message.reply_text('Не удалось получить ответ от модели.', reply_markup=get_main_buttons(user_id, current_model_id))

async def process_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        await update.message.reply_text("У вас нет доступа к этому боту.")
        return
    if 'current_provider' not in user_state.get(user_id, {}) or not user_state[user_id].get('current_provider'):
        await update.message.reply_text("Пожалуйста, выберите провайдера из предоставленных кнопок.", reply_markup=get_provider_buttons())
        return
    user_text = update.message.text
    current_model_id = user_state[user_id].get('current_model_id')
    current_chat_slot = user_state[user_id].get('current_chat_slot')
    if not current_model_id or not current_chat_slot:
        await update.message.reply_text('Модель или чат не выбраны.', reply_markup=get_main_buttons(user_id))
        return
    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai').lower()
    model_type = model_info.get('type', 'text').lower()
    # Проверка доступности модели
    if provider == 'cablyai':
        global_tokens = usage_store.get('cablyai_tokens', 0)
        admin_exempt_threshold = config['limits']['cablyai'].get('admin_exempt_threshold', 150000)
        if global_tokens >= config['limits']['cablyai']['daily_token_limit'] - admin_exempt_threshold:
            if user_id not in AUTHORIZED_MODEL_USERS:
                await update.message.reply_text('Доступ к CatAI моделям временно закрыт.', reply_markup=get_main_buttons(user_id, current_model_id))
                return
    elif provider in ['helix', 'meowai', 'rimunace']:
        if not check_and_increment_limit(user_id, provider, current_model_id):
            await update.message.reply_text('Вы достигли лимита запросов для этого провайдера.', reply_markup=get_main_buttons(user_id, current_model_id))
            return

    # Изменено: перевод только для моделей типа 'image'
    if model_type == 'image' and user_text:
        translated_text = await translate_to_english(user_text, user_id, context)
        if translated_text:
            user_text = translated_text
        else:
            await update.message.reply_text("Не удалось перевести текст.")
            return

    if model_type != 'image':
        system_instruction = SYSTEM_INSTRUCTION_TEXT
        custom_prompt = custom_prompts.get(str(user_id), "")
        if custom_prompt and current_model_id not in RESTRICTED_MODELS:
            system_instruction = custom_prompt
        context_store.setdefault(user_id, {}).setdefault(current_model_id, {}).setdefault(current_chat_slot, load_chat_context(user_id, current_model_id, current_chat_slot, system_instruction))
        context_store[user_id][current_model_id][current_chat_slot].append({"role": "user", "content": user_text})

    answer = await get_model_response(provider, current_model_id, context_store[user_id][current_model_id][current_chat_slot], model_type, user_id, user_text if model_type == 'image' else None)
    if answer:
        context_store[user_id][current_model_id][current_chat_slot].append({"role": "assistant", "content": answer})
        save_chat_context(user_id, current_model_id, current_chat_slot, context_store[user_id][current_model_id][current_chat_slot])
        mode = user_mode_store.get(str(user_id), 'message')
        if model_type != 'image':
            if mode == 'message':
                answer_html = markdown_to_html(answer)
                for msg in split_message(answer_html, MAX_MESSAGE_LENGTH):
                    await update.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
            elif mode == 'file':
                file_path_generated = save_md_file(answer, user_id)
                if file_path_generated:
                    try:
                        with open(file_path_generated, 'rb') as f:
                            await update.message.reply_document(
                                document=InputFile(f),
                                filename=os.path.basename(file_path_generated),
                                caption="Твой ответ модели в формате markdown.",
                                reply_markup=get_main_buttons(user_id, current_model_id)
                            )
                        os.remove(file_path_generated)
                    except Exception as e:
                        logger.error(f"Ошибка отправки файла: {e}")
                        await update.message.reply_text('Не удалось отправить файл.')
                else:
                    await update.message.reply_text('Не удалось создать файл с ответом.')
        else:
            if provider == 'cablyai':
                try:
                    await update.message.reply_photo(photo=answer, caption="твоя картинка:", reply_markup=get_main_buttons(user_id, current_model_id))
                except TelegramError as e:
                    logger.error(f"Ошибка отправки изображения: {e}")
                    await update.message.reply_text("Не удалось отправить изображение.")
            else:
                if answer.startswith('http'):
                    try:
                        await update.message.reply_photo(photo=answer, caption="твоя картинка:", reply_markup=get_main_buttons(user_id, current_model_id))
                    except TelegramError as e:
                        logger.error(f"Ошибка отправки изображения: {e}")
                        await update.message.reply_text("Не удалось отправить изображение.")
                else:
                    await update.message.reply_text(answer, reply_markup=get_main_buttons(user_id, current_model_id))
    else:
        if provider == 'cablyai':
            await update.message.reply_text('Не удалось получить ответ от модели или превышен лимит токенов.', reply_markup=get_main_buttons(user_id, current_model_id))
        else:
            await update.message.reply_text('Не удалось получить ответ от модели.', reply_markup=get_main_buttons(user_id, current_model_id))

# Обработчик ошибок
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE):
    logger.error(msg="Exception while handling an update:", exc_info=context.error)
    if isinstance(update, Update) and update.effective_user:
        try:
            user_id = update.effective_user.id
            current_model_id = user_state.get(user_id, {}).get('current_model_id')
            await update.effective_message.reply_text('Произошла ошибка. Пожалуйста, попробуйте позже.', reply_markup=get_main_buttons(user_id, current_model_id))
        except TelegramError as e:
            logger.error(f"Не удалось отправить сообщение об ошибке пользователю: {e}")

# Фоновая задача для сброса лимитов
async def reset_limits_daily(application):
    while True:
        now = datetime.now(ZoneInfo('Europe/Moscow'))
        reset_time_str = config.get('limit_reset_time', '02:00')
        reset_hour, reset_minute = map(int, reset_time_str.split(':'))
        next_reset = now.replace(hour=reset_hour, minute=reset_minute, second=0, microsecond=0)
        if now >= next_reset:
            next_reset += timedelta(days=1)
        sleep_duration = (next_reset - now).total_seconds()
        logger.info(f"Сброс лимитов через {sleep_duration} секунд.")
        await asyncio.sleep(sleep_duration)
        # Reset global limits
        usage_store['cablyai_tokens'] = 0
        usage_store['cablyai_images_generated'] = 0  # Сброс лимита изображений
        usage_store['helix_requests'] = 0
        usage_store['rimunace_credits'] = 0
        # MeowAI не сбрасывается
        # Reset per-user limits for daily limits
        for uid, limits in config.get('user_limits', {}).items():
            if 'cablyai_tokens' in usage_store.get('users', {}).get(uid, {}):
                if limits.get('cablyai', None) != "infinite":
                    usage_store['users'][uid]['cablyai_tokens'] = 0
            if 'helix_requests' in usage_store.get('users', {}).get(uid, {}):
                if limits.get('helix', None) != "infinite":
                    usage_store['users'][uid]['helix_requests'] = 0
            if 'rimunace_requests' in usage_store.get('users', {}).get(uid, {}):
                if limits.get('rimunace', {}).get('per_user_daily_requests', None) != "infinite":
                    usage_store['users'][uid]['rimunace_requests'] = 0
            # MeowAI не сбрасывается
        save_json('usage.json', usage_store)
        # Уведомляем пользователей
        for uid in AUTHORIZED_USER_IDS:
            try:
                await application.bot.send_message(chat_id=uid, text="Дневные лимиты сброшены. Вы можете продолжать использовать бота.")
            except Exception as e:
                logger.error(f"Не удалось отправить сообщение пользователю {uid}: {e}")

# Основная функция запуска бота
def main():
    TELEGRAM_TOKEN = config.get('telegram_token')
    if not TELEGRAM_TOKEN:
        logger.error("Токен бота не указан в config.json.")
        exit(1)
    application = ApplicationBuilder().token(TELEGRAM_TOKEN).defaults(Defaults(parse_mode=ParseMode.HTML)).build()

    # Обработчики команд
    application.add_handler(CommandHandler("start", start_command))
    application.add_handler(CommandHandler("setmode", setmode))
    application.add_handler(CommandHandler("mode", mode_command_func))
    application.add_handler(CommandHandler("models", models_command))
    application.add_handler(CommandHandler("reload", reload_bot))
    application.add_handler(CommandHandler("limits", limits_command))
    application.add_handler(CommandHandler("adduser", add_user))
    application.add_handler(CommandHandler("custom", custom_command))
    application.add_handler(CommandHandler("clearcustom", clearcustom_command))
    # обработчик команды /instructions
    async def instructions(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
        """
        Отправляет пользователю инструкции по использованию бота.
        """
        menu_text = (
        "<b>Инструкция по использованию бота:</b>\n\n"
        "<b>/start</b> - запустить бота и выбрать модель для взаимодействия.\n"
        "<b>/setmode [message или file]</b> - выбрать режим получения ответов (message - текстовые сообщения, file - .md файлы, они нужны для того чтобы получить полный ответ модели без ограничения длины сообщения телеграма).\n"
        "<b>/mode</b> - узнать текущий выбранный режим.\n"
        "<b>/instructions</b> - получить инструкции по использованию бота.\n"
        "<b>/models</b> - получить описание доступных моделей.\n"
        "<b>/limits</b> - посмотреть ваши текущие лимиты и оставшиеся запросы на сегодня.\n"
        "<b>/custom</b> - установить пользовательский системный промпт.\n"
        "<b>/clearcustom</b> - очистить пользовательский системный промпт.\n\n"
        "Кнопки:\n"
        "<b>сменить модель</b> - выбрать другую модель.\n"
        "<b>продолжить ответ</b> - продолжить ответ модели.\n"
        "‼️<b>Токены:</b> токен не равен символу. Все модели воспринимают токен как единицу измерения по-разному, будь то слово, кусок слова или символ. Если про модель указано, что она отвечает определённым количеством символов - так и есть.\n"
        "‼️<b>Лимиты бота:</b> для CatAI: 2 млн токенов/день на всех юзеров бота;\nдля OpenAI: сейчас недоступен, нету рабочих API ключей;\nдля Helix: 50 запросов/день на всех юзеров бота;\nдля MeowAI: 960 (сейчас) запросов на всех юзеров бота. Не восполняются;\nдля Rimunace: 500 кредитов/день и 15 запросов/день к определённым моделям.\nАдмин бота не гарантирует, что бот сможет работать бесконечно, всё зависит от провайдеров API ключей. Админ постоянно пытается увеличить кол-во провайдеров для большего разнообразия моделей и большей стабильности.\n"
        "‼️<b>Контекст:</b> в боте доступны чаты - 4 на каждую модель. Ты можешь очистить контекст определённого чата, если надо."
        "‼️<b>модели с 🆓 ПОЛНОСТЬЮ бесплатны и безлимитны. общайся сколько угодно! и лимиты твои не тратят.</b>"
        )
        await update.message.reply_text(menu_text, parse_mode=ParseMode.HTML)
    application.add_handler(CommandHandler("instructions", instructions))

    # Обработчики CallbackQuery
    application.add_handler(CallbackQueryHandler(handle_callback_query, pattern='^(model_select:.+|chat_slot:\d+:.+|change_model|change_provider|clear_context|continue|next_chats|back_chats)$'))

    # Обработчики сообщений
    application.add_handler(MessageHandler(filters.Regex('^(CatAI|OpenAI|Helix|MeowAI|Rimunace)$') & filters.TEXT, handle_provider_selection))
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, process_message))
    application.add_handler(MessageHandler(filters.PHOTO, handle_photo))
    application.add_handler(MessageHandler(filters.Document.ALL, handle_document))

    # Обработчик ошибок
    application.add_error_handler(error_handler)

    # Запуск фоновой задачи для сброса лимитов
    application.job_queue.run_once(lambda context: asyncio.create_task(reset_limits_daily(application)), when=0)

    # Запуск бота
    application.run_polling()

if __name__ == '__main__':
    main()