привет! вот код моего телеграм бота и config.json для него. ( но сначала идёт config.json ). ознакомься с ними. помоги вот с чем:
я изменил айди моделей openai в config.json удалив openai- в начале каждого из них ( без этого никак! несмотря на разных провайдеров, айди моделей одинаковый. )
и теперь когда я выбираю любую модель созданную openai ( chatgpt-4o-latest, gpt-4-turbo, gpt-4o-mini, o1-preview, o1-mini ) но предоставляемую cablyai, диалог все равно ведётся с этой моделью но предоставляемой openai. и пользователю при выборе модели для чата так и пишется, что он выбрал модель с (openai) даже если он выбрал эту модель но предоставляемую cablyai. попробуй это исправить! 
и ещё. попробуй добавить моделям chatgpt-4o-latest, gpt-4-turbo, gpt-4o-mini предоставляемым openai возможность получать, обрабатывать и отвечать на изображения! конечно же руководствуясь документацией openai api.
после внесения изменений - скинь полный изменённый код бота. 
{
  "telegram_token": "токен",
  "api_key": "ключcablyai",
  "openai_api_key": "ключopenai",
  "authorized_user_ids": [
      5005816138,
      7856892361,
      5643392602,
      7193906122,
      5075150336,
      1759650343
  ],
  "models": {
      "⚡️Claude 3.5 Sonnet": {
          "id": "claude-3-5-sonnet-20241022",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "Claude 3 Opus": {
          "id": "claude-3-opus",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "⚡️GPT-4o-latest": {
          "id": "chatgpt-4o-latest",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "GPT-4 Turbo": {
          "id": "gpt-4-turbo",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "GPT-4o mini": {
          "id": "gpt-4o-mini",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "💥o1-preview": {
          "id": "o1-preview",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "💥o1-mini": {
          "id": "o1-mini",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "LLaMA 3.1 405b": {
          "id": "llama-3.1-405b-turbo",
          "type": "multimodal",
          "provider": "cablyai"
      },
      "✨Flux 1.1 Pro": {
          "id": "FLUX.1.1 [pro]",
          "type": "image",
          "provider": "cablyai"
      },
      "Stable Diffusion 3.5": {
          "id": "sd3.5",
          "type": "image",
          "provider": "cablyai"
      },
      "⚡️GPT-4o-latest (openai)": {
          "id": "chatgpt-4o-latest",
          "type": "multimodal",
          "provider": "openai"
      },
      "GPT-4 Turbo (openai)": {
          "id": "gpt-4-turbo",
          "type": "multimodal",
          "provider": "openai"
      },
      "GPT-4o mini (openai)": {
          "id": "gpt-4o-mini",
          "type": "multimodal",
          "provider": "openai"
      },
      "💥o1-preview (openai)": {
          "id": "o1-preview",
          "type": "multimodal",
          "provider": "openai"
      },
      "💥o1-mini (openai)": {
          "id": "o1-mini",
          "type": "multimodal",
          "provider": "openai"
      }
  },
  "system_instruction": "ты - хороший ассистент, готовый ответить на любой вопрос."
}

import logging
import requests
import json
import os
import re
import html
from datetime import datetime
from telegram import (
    Update,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    InputFile
)
from telegram.constants import ParseMode
from telegram.ext import (
    ApplicationBuilder,
    CommandHandler,
    MessageHandler,
    ContextTypes,
    filters,
    CallbackQueryHandler,
    Defaults,
)
from telegram.error import TelegramError
import openai

# Функция для загрузки конфигурации
def load_config():
    script_dir = os.path.dirname(os.path.abspath(__file__))
    config_path = os.path.join(script_dir, 'config.json')
    
    if not os.path.exists(config_path):
        raise FileNotFoundError(f"Файл config.json не найден по пути: {config_path}")
    
    with open(config_path, 'r', encoding='utf-8') as f:
        return json.load(f)

# Функция для сохранения контекста чата в файл
def save_chat_context(user_id, model_id, chat_slot, context):
    chats_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'chats')
    if not os.path.exists(chats_dir):
        os.makedirs(chats_dir)
    
    filename = f"{user_id}_{model_id}_chat{chat_slot}.json"
    file_path = os.path.join(chats_dir, filename)
    
    try:
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(context, f, ensure_ascii=False, indent=4)
    except Exception as e:
        logger.error(f"Ошибка при сохранении контекста чата: {e}")

# Функция для загрузки контекста чата из файла
def load_chat_context(user_id, model_id, chat_slot, system_instruction):
    chats_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'chats')
    filename = f"{user_id}_{model_id}_chat{chat_slot}.json"
    file_path = os.path.join(chats_dir, filename)
    
    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"Ошибка при загрузке контекста чата: {e}")
        return [{"role": "system", "content": system_instruction}]

# Загрузка конфигурации
try:
    config = load_config()
except FileNotFoundError as e:
    print(e)
    exit(1)
except json.JSONDecodeError as e:
    print(f"Ошибка разбора config.json: {e}")
    exit(1)

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

# Настройка OpenAI API
openai_api_key = config.get('openai_api_key')
if not openai_api_key:
    logger.error("OpenAI API ключ не указан в config.json.")
    exit(1)
openai.api_key = openai_api_key

# Ваши API-эндпоинты (cablyai)
API_URL_TEXT = 'https://cablyai.com/v1/chat/completions'
API_URL_IMAGE = 'https://cablyai.com/v1/images/generations'  # Используем ваш актуальный URL для генерации изображений
API_HEADERS = {
    'Authorization': f"Bearer {config['api_key']}",
    'Content-Type': 'application/json'
}

# Инструкция для текстовых моделей
SYSTEM_INSTRUCTION_TEXT = config.get('system_instruction', "ты ведёшь себя как котик")

# Структуры для хранения контекста и активных чатов
# context_store = {user_id: {model_id: {chat_slot: [messages]}}}
context_store = {}
# active_chat_slot = {user_id: {model_id: chat_slot_number}}
active_chat_slot = {}

MAX_MESSAGE_LENGTH = 4096

# Преобразуем список авторизованных пользователей в множество для быстрого поиска
AUTHORIZED_USER_IDS = set(config.get('authorized_user_ids', []))

# Хранение режима пользователя ('message' или 'file')
user_mode_store = {}

# Создание маппинга model_id -> model_info для быстрого доступа
model_id_to_info = {}
model_id_to_name = {}
for model_name, model_info in config.get('models', {}).items():
    model_id = model_info.get('id')
    if model_id:
        model_id_to_info[model_id] = model_info
        model_id_to_name[model_id] = model_name
    else:
        logger.warning(f"Модель '{model_name}' не имеет 'id'.")

# Список ограниченных моделей
RESTRICTED_MODELS = ['o1-preview', 'o1-mini']
AUTHORIZED_MODEL_USERS = {5005816138, 1759650343, 7856892361}  # Пример ID пользователей

# Описания моделей (примерные, необходимо доработать)
MODEL_DESCRIPTIONS = {
    "claude-3-5-sonnet-20241022": "⚡️Claude 3.5 Sonnet — передовая модель Anthropic. быстрая, в задачах с кодом в чем-то лучше даже OpenAI o1-mini и preview. актуальность знаний: до апреля 2024. контекстное окно: 200000 токенов. выплюнуть за раз может: 5000 символов (символ не равен токен).",
    "claude-3-opus": "Claude 3 Opus — бывшая передовая модель Anthropic. медленная, но очень хороша в генерации текстов и удивляет своими познаниями русского языка. актуальность знаний: до августа 2023. контекстное окно: 200000 токенов. выплюнуть за раз может: 4096 токенов.",
    "chatgpt-4o-latest": "⚡️GPT-4o-latest — новейшая версия модели GPT-4o, созданная OpenAI. быстрая, хороша для широкого спектра задач. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 16384 токена.",
    "gpt-4-turbo": "GPT-4 Turbo — бывшая передовая модель OpenAI. иногда в чем-то превосходит даже GPT-4o-latest, но уступает в скорости. актуальность знаний: до декабря 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 4096 токенов.",
    "gpt-4o-mini": "GPT-4o mini — уменьшенная версия GPT-4o. очень быстрая! но по качеству ответов уступает. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 16384 токена.",
    "o1-preview": "💥o1-preview — передовая модель OpenAI. использует цепочку рассуждений для ответов, что повышает их точность и размер, но генерирует ответ довольно долго относительно других моделей. в точных науках и коде показывает себя лучше любых моделей. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 32768 токенов.",
    "o1-mini": "💥o1-mini — уменьшенная версия модели o1 от OpenAI. тоже использует цепочку рассуждений, но думает намного быстрее. может уступать o1-preview в качестве ответов, но это компенсируется её на 80% более низкой ценой (для тех кто платит за неё) и быстротой ответов. актуальность знаний: до октября 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: 65536 токенов.",
    "llama-3.1-405b-turbo": "LLaMA 3.1 405b — текстовая модель. сопоставима по качеству ответов с остальными моделями, но может уступать, и выдавать всякие иероглифы посреди ответа. но почему бы и не добавить? актуальность знаний: до декабря 2023. контекстное окно: 128000 токенов. выплюнуть за раз может: неизвестно.",
    "FLUX.1.1 [pro]": "✨Flux 1.1 Pro — лучшая существующая модель генерации изображений. некоторые её изображения неотличимы от реальных! но она нестабильна. но ты всегда можешь отправить новый запрос! контекста не имеет, работая лишь с текущим заданным запросом.",
    "sd3.5": "Stable Diffusion 3.5 — модель для генерации изображений по текстовому описанию. контекста не имеет, работая лишь с текущим заданным запросом.",
    "openai-chatgpt-4o-latest": "⚡️GPT-4o-latest (openai) — новейшая версия модели GPT-4o от OpenAI. быстрая, хороша для широкого спектра задач. поддерживает обработку текста и изображений.",
    "openai-gpt-4-turbo": "GPT-4 Turbo (openai) — бывшая передовая модель OpenAI. иногда в чем-то превосходит даже GPT-4o-latest, но уступает в скорости. поддерживает обработку текста и изображений.",
    "openai-gpt-4o-mini": "GPT-4o mini (openai) — уменьшенная версия GPT-4o. очень быстрая! но по качеству ответов уступает. поддерживает обработку текста и изображений.",
    "openai-o1-preview": "💥o1-preview (openai) — передовая модель OpenAI. использует цепочку рассуждений для ответов, что повышает их точность и размер, но генерирует ответ довольно долго относительно других моделей. поддерживает только текстовую обработку.",
    "openai-o1-mini": "💥o1-mini (openai) — уменьшенная версия модели o1 от OpenAI. тоже использует цепочку рассуждений, но думает намного быстрее. поддерживает только текстовую обработку."
}

# Функция для разбивки длинного сообщения на части
def split_message(message, max_length):
    return [message[i:i + max_length] for i in range(0, len(message), max_length)]

# Функция для создания кнопок выбора модели
def get_model_buttons(user_id):
    buttons = []
    for name, model_info in config.get('models', {}).items():
        model_id = model_info['id']
        provider = model_info.get('provider', 'cablyai')
        if provider == 'cablyai':
            if model_id in RESTRICTED_MODELS and user_id not in AUTHORIZED_MODEL_USERS:
                # Добавляем кнопку, но доступ к модели ограничен
                buttons.append([InlineKeyboardButton(html.escape(name.lower()), callback_data=f"restricted:{model_id}")])
            else:
                buttons.append([InlineKeyboardButton(html.escape(name.lower()), callback_data=f"model_select:{model_id}")])
        elif provider == 'openai':
            buttons.append([InlineKeyboardButton(html.escape(name.lower()), callback_data=f"model_select:{model_id}")])
    return InlineKeyboardMarkup(buttons)

# Функция для создания кнопок выбора слота чата
def get_chat_slot_buttons(model_id):
    keyboard = [
        [
            InlineKeyboardButton("Чат 1", callback_data=f"chat_slot:1:{model_id}"),
            InlineKeyboardButton("Чат 2", callback_data=f"chat_slot:2:{model_id}")
        ]
    ]
    return InlineKeyboardMarkup(keyboard)

# Функция для создания основных кнопок: 'сменить модель', 'выбрать чат', 'очистить контекст'
def get_main_buttons(user_id, model_id=None):
    buttons = []
    if model_id:
        buttons.append([
            InlineKeyboardButton("Сменить модель", callback_data='change_model'),
            InlineKeyboardButton("Выбрать чат", callback_data='change_chat_slot'),
            InlineKeyboardButton("Очистить контекст", callback_data='clear_context')
        ])
    else:
        buttons.append([
            InlineKeyboardButton("Сменить модель", callback_data='change_model')
        ])
    return InlineKeyboardMarkup(buttons)

# Функция для преобразования Markdown в HTML
def markdown_to_html(text):
    """
    Простой конвертер Markdown в HTML.
    Обрабатывает заголовки, **жирный**, *курсив*, __подчёркивание__, ~~зачёркивание~~.
    """
    # Экранирование HTML-символов
    text = html.escape(text)
    
    # Замена заголовков: ### Заголовок -> <b>Заголовок</b>
    for i in range(6, 0, -1):  # Обрабатываем заголовки от h6 до h1
        pattern = r'^{0} (.+)$'.format('#' * i)
        replacement = r'<b>\1</b>'
        text = re.sub(pattern, replacement, text, flags=re.MULTILINE)
    
    # Замена **жирный** на <b>жирный</b>
    text = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', text)
    
    # Замена *курсив* на <i>курсив</i>
    text = re.sub(r'\*(.*?)\*', r'<i>\1</i>', text)
    
    # Замена __подчёркивание__ на <u>подчёркивание</u>
    text = re.sub(r'__(.*?)__', r'<u>\1</u>', text)
    
    # Замена ~~зачёркивание~~ на 
зачёркивание

    text = re.sub(r'~~(.*?)~~', r'
\1
', text)
    
    return text

# Функция для сохранения ответа в .md файл
def save_md_file(content, user_id):
    """
    Сохраняет содержимое в .md файл с уникальным именем.
    Возвращает путь к файлу.
    """
    if not os.path.exists('responses'):
        os.makedirs('responses')
    
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    filename = f"responses/{user_id}_{timestamp}.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):
    model_info = model_id_to_info.get(model_id, {})
    provider = model_info.get('provider', 'cablyai')
    if provider == 'cablyai':
        return False  # Cablyai модели не поддерживают обработку изображений
    elif provider == 'openai':
        # Исключаем o1-preview и o1-mini
        if model_id in ['openai-o1-preview', 'openai-o1-mini']:
            return False
        return True
    return False

# Функция для отправки запроса через cablyai API
def send_cablyai_request(model_id, messages):
    data = {
        "model": model_id,
        "messages": messages
    }
    try:
        response = requests.post(API_URL_TEXT, headers=API_HEADERS, json=data)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка запроса к cablyai API: {e}")
        return None

# Функция для отправки запроса через OpenAI API
def send_openai_request(model_id, messages=None, prompt=None):
    try:
        if model_id.startswith("openai-"):
            base_model = model_id
            if "image" in model_id.lower():
                # Обработка изображений (если необходимо, можно добавить логику)
                pass
            if "chat" in base_model:
                response = openai.ChatCompletion.create(
                    model=base_model,
                    messages=messages
                )
                return response
            else:
                # Для других типов моделей (например, Image Generation) можно добавить обработку
                response = openai.ChatCompletion.create(
                    model=base_model,
                    messages=messages
                )
                return response
        else:
            logger.error(f"Модель не предназначена для OpenAI API: {model_id}")
            return None
    except openai.error.OpenAIError as e:
        logger.error(f"Ошибка OpenAI API: {e}")
        return None

# Обработчик команды /setmode
async def setmode(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """
    Позволяет пользователю выбрать режим получения ответов:
    - message: получение текстовых сообщений
    - file: получение ответов в .md файлах
    """
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался использовать /setmode: {user_id}")
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return
    
    if len(context.args) != 1:
        await update.message.reply_text('использование: /setmode [message|file]')
        return
    
    mode = context.args[0].lower()
    if mode not in ['message', 'file']:
        await update.message.reply_text('неверный режим. используй "message" или "file".')
        return
    
    user_mode_store[user_id] = mode
    await update.message.reply_text(f'режим получения ответов установлен на: <b>{mode}</b>', parse_mode=ParseMode.HTML)

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

# Обработчик команды /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>/get_mode</b> - узнать текущий выбранный режим.\n"
        "<b>/instructions</b> - получить инструкции по использованию бота.\n"
        "<b>/models</b> - получить описание доступных моделей.\n\n"
        "также доступно использование кнопок ниже:\n"
        "<b>сменить модель</b> - выбрать другую модель для общения.\n"
        "<b>выбрать чат</b> - переключиться между чатами (Чат 1 или Чат 2).\n"
        "<b>очистить контекст</b> - очистить историю сообщений текущего чата.\n"
        "‼️<b>важно:</b> токен не равен символу. все модели воспринимают токен как единицу измерения по-разному, будь то слово, кусок слова или символ. если про модель указано что она отвечает определённым количеством символов - так и есть.\n"
        "‼️<b>лимиты бота:</b> если у тебя есть доступ к этому боту - лимитов никаких нет кроме как на o1 модели (если тебе к ним открыт доступ). админ бота не гарантирует что бот сможет работать бесконечно, всё зависит от провайдера API ключей.\n"
        "‼️<b>контекст:</b> бот сохраняет контекст для каждого чата. ты можешь переключаться между чатами или очищать контекст по необходимости."
    )
    
    # Отправляем инструкции как сообщение без кнопок
    await update.message.reply_text(menu_text, parse_mode=ParseMode.HTML)

# Обработчик команды /models
async def models_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """
    Отправляет пользователю список доступных моделей с описаниями.
    """
    user_id = update.effective_user.id
    models = config.get('models', {})
    if not models:
        await update.message.reply_text('нет доступных моделей.', reply_markup=get_main_buttons(user_id=user_id))
        return
    
    message = "<b>описание доступных моделей:</b>\n\n"
    for name, info in models.items():
        model_id = info.get('id')
        model_name = name.lower()
        model_type = info.get('type', 'неизвестный тип').lower()
        description = MODEL_DESCRIPTIONS.get(model_id, "описание недоступно.")
        message += f"- <b>{model_name}</b> ({model_type}): {description}\n"
    
    await update.message.reply_text(message, parse_mode=ParseMode.HTML)

# Обработчик команды /start
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался использовать /start: {user_id}")
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return

    chat_id = update.effective_chat.id

    # Инициализация структуры данных для пользователя
    if user_id not in context_store:
        context_store[user_id] = {}
    if user_id not in active_chat_slot:
        active_chat_slot[user_id] = {}
    
    # Кнопки для выбора модели
    if config.get('models'):
        await update.message.reply_text(
            'привет! выбери модель для общения:',
            reply_markup=get_model_buttons(user_id)
        )
    else:
        await update.message.reply_text(
            'привет!\n\nнет доступных моделей для выбора.',
            reply_markup=get_main_buttons(user_id)
        )

# Обработчик выбора модели через кнопки
async def select_model(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался выбрать модель: {user_id}")
        await query.edit_message_text('тебе сюда мяукать нельзя.')
        return

    selected_model_data = query.data

    # Проверка, является ли модель ограниченной
    if selected_model_data.startswith("restricted:"):
        model_id = selected_model_data.split("restricted:")[1]
        if user_id not in AUTHORIZED_MODEL_USERS:
            logger.warning(f"Пользователь {user_id} пытался использовать ограниченную модель {model_id}")
            await query.edit_message_text('вам нельзя использовать эту модель.')
            return
    elif selected_model_data.startswith("model_select:"):
        model_id = selected_model_data.split("model_select:")[1]
    else:
        logger.error(f"Неправильный формат данных callback: {selected_model_data}")
        await query.edit_message_text('произошла ошибка при выборе модели.')
        return

    model_info = model_id_to_info.get(model_id)

    if not model_info:
        logger.error(f"Выбранная модель не найдена в конфигурации: {model_id}")
        await query.edit_message_text('выбранная модель не найдена в конфигурации.')
        return

    # Предлагаем выбрать слот чата (1 или 2)
    await query.edit_message_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))

# Обработчик выбора слота чата
async def select_chat_slot(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    data = query.data  # Формат: "chat_slot:{slot}:{model_id}"
    try:
        _, slot_str, model_id = data.split(":")
        chat_slot = int(slot_str)
    except ValueError:
        logger.error(f"Некорректные данные callback для выбора чата: {data}")
        await query.edit_message_text('произошла ошибка при выборе чата.')
        return

    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался выбрать чат: {user_id}")
        await query.edit_message_text('тебе сюда мяукать нельзя.')
        return

    # Устанавливаем активный слот чата
    if user_id not in active_chat_slot:
        active_chat_slot[user_id] = {}
    active_chat_slot[user_id][model_id] = chat_slot

    # Загружаем контекст чата
    model_info = model_id_to_info.get(model_id, {})
    provider = model_info.get('provider', 'cablyai')
    system_instruction = SYSTEM_INSTRUCTION_TEXT
    if provider == 'openai':
        system_instruction = "You are a helpful assistant."  # Можно изменить по необходимости

    context_store.setdefault(user_id, {}).setdefault(model_id, {})
    if chat_slot not in context_store[user_id][model_id]:
        context_store[user_id][model_id][chat_slot] = load_chat_context(user_id, model_id, chat_slot, system_instruction)
    else:
        # Если контекст уже загружен, ничего не делаем
        pass

    model_name = html.escape(model_id_to_name.get(model_id, "Неизвестная модель"))
    await query.edit_message_text(f'выбран <b>Чат {chat_slot}</b> для модели <b>{model_name}</b>', parse_mode=ParseMode.HTML, reply_markup=get_main_buttons(user_id, model_id))

# Обработчик кнопки "сменить модель"
async def change_model_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался сменить модель: {user_id}")
        await query.edit_message_text('тебе сюда мяукать нельзя.')
        return

    # Отображаем кнопки выбора модели
    await query.edit_message_text('выбери модель для взаимодействия:', reply_markup=get_model_buttons(user_id))

# Обработчик кнопки "выбрать чат"
async def change_chat_slot_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался выбрать чат: {user_id}")
        await query.edit_message_text('тебе сюда мяукать нельзя.')
        return

    # Получаем текущие модели для пользователя
    current_models = active_chat_slot.get(user_id, {})
    if not current_models:
        await query.edit_message_text('модель не выбрана. используйте /start для выбора модели.')
        return

    # Предлагаем выбрать модель для смены чата
    buttons = []
    for model_id in current_models.keys():
        model_name = html.escape(model_id_to_name.get(model_id, model_id))
        buttons.append([InlineKeyboardButton(model_name, callback_data=f"choose_chat_model:{model_id}")])
    reply_markup = InlineKeyboardMarkup(buttons)

    await query.edit_message_text('выбери модель, для которой хочешь сменить чат:', reply_markup=reply_markup)

# Обработчик выбора модели для смены чата
async def choose_chat_model(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    data = query.data  # Формат: "choose_chat_model:{model_id}"
    try:
        _, model_id = data.split(":")
    except ValueError:
        logger.error(f"Некорректные данные callback для выбора модели чата: {data}")
        await query.edit_message_text('произошла ошибка при выборе модели.')
        return

    # Предлагаем выбрать слот чата
    await query.edit_message_text(f'выбери слот для модели <b>{html.escape(model_id_to_name.get(model_id, "Неизвестная модель"))}</b>:', parse_mode=ParseMode.HTML, reply_markup=get_chat_slot_buttons(model_id))

# Обработчик кнопки "очистить контекст"
async def clear_context_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался очистить контекст: {user_id}")
        await query.edit_message_text('тебе сюда мяукать нельзя.')
        return

    # Получаем текущие модели для пользователя
    current_models = active_chat_slot.get(user_id, {})
    if not current_models:
        await query.edit_message_text('модель не выбрана. используйте /start для выбора модели.')
        return

    # Предлагаем выбрать модель для очистки контекста
    buttons = []
    for model_id in current_models.keys():
        model_name = html.escape(model_id_to_name.get(model_id, model_id))
        buttons.append([InlineKeyboardButton(model_name, callback_data=f"clear_context_model:{model_id}")])
    reply_markup = InlineKeyboardMarkup(buttons)

    await query.edit_message_text('выбери модель, контекст которой хочешь очистить:', reply_markup=reply_markup)

# Обработчик выбора модели для очистки контекста
async def clear_context_model(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    data = query.data  # Формат: "clear_context_model:{model_id}"
    try:
        _, model_id = data.split(":")
    except ValueError:
        logger.error(f"Некорректные данные callback для очистки контекста модели: {data}")
        await query.edit_message_text('произошла ошибка при выборе модели.')
        return

    # Получаем активный чат для модели
    chat_slot = active_chat_slot.get(update.effective_user.id, {}).get(model_id)
    if not chat_slot:
        await query.edit_message_text('активный чат не найден для выбранной модели.')
        return

    # Очищаем контекст
    model_info = model_id_to_info.get(model_id, {})
    provider = model_info.get('provider', 'cablyai')
    system_instruction = SYSTEM_INSTRUCTION_TEXT
    if provider == 'openai':
        system_instruction = "You are a helpful assistant."  # Можно изменить по необходимости

    context_store[update.effective_user.id][model_id][chat_slot] = [{"role": "system", "content": system_instruction}]
    save_chat_context(update.effective_user.id, model_id, chat_slot, context_store[update.effective_user.id][model_id][chat_slot])

    await query.edit_message_text(f'контекст <b>Чат {chat_slot}</b> для модели <b>{html.escape(model_id_to_name.get(model_id, "Неизвестная модель"))}</b> очищен.', parse_mode=ParseMode.HTML, reply_markup=get_main_buttons(update.effective_user.id, model_id))

# Обработчик выбора слота чата для продолжения
async def continue_response(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    await query.answer()

    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался продолжить ответ: {user_id}")
        await query.edit_message_text('тебе сюда мяукать нельзя.')
        return

    # Получаем текущую модель и активный чат слота
    user_active_slots = active_chat_slot.get(user_id, {})
    if not user_active_slots:
        await query.edit_message_text('модель не выбрана. используйте /start для выбора модели.')
        return

    # Берем первую выбранную модель
    current_model_id = next(iter(user_active_slots))
    current_chat_slot = user_active_slots[current_model_id]

    model_info = model_id_to_info.get(current_model_id)
    if not model_info:
        logger.error(f"Модель не найдена в конфигурации для чата {update.effective_chat.id}: {current_model_id}")
        await query.edit_message_text('модель не найдена в конфигурации.', reply_markup=get_main_buttons(user_id, current_model_id))
        return

    selected_model_type = model_info.get('type', 'text')
    provider = model_info.get('provider', 'cablyai')

    # Проверка доступа к модели
    if current_model_id in RESTRICTED_MODELS and user_id not in AUTHORIZED_MODEL_USERS:
        logger.warning(f"Пользователь {user_id} пытался продолжить ответ в ограниченной модели {current_model_id}")
        await query.edit_message_text('вам нельзя использовать эту модель.', reply_markup=get_main_buttons(user_id, current_model_id))
        return

    # Получаем режим пользователя
    mode = user_mode_store.get(user_id, 'message')

    if selected_model_type in ['text', 'multimodal']:
        # Добавление сообщения пользователя для продолжения
        context_store[user_id][current_model_id][current_chat_slot].append({"role": "user", "content": "продолжи."})
        save_chat_context(user_id, current_model_id, current_chat_slot, context_store[user_id][current_model_id][current_chat_slot])

        # Подготовка данных для API с учетом контекста
        data = {
            "model": current_model_id,
            "messages": context_store[user_id][current_model_id][current_chat_slot]
        }

        if provider == 'cablyai':
            api_url = API_URL_TEXT
            response_data = send_cablyai_request(current_model_id, data['messages'])
        elif provider == 'openai':
            response = send_openai_request(current_model_id, messages=data['messages'])
            response_data = response.to_dict() if response else None
        else:
            response_data = None

        if response_data and response_data.get('choices'):
            answer = response_data["choices"][0].get('message', {}).get('content', 'что-то пошло не так. мяу :(')
            
            if mode == 'message':
                # Преобразование Markdown в HTML
                answer_html = markdown_to_html(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])
                
                # Разбивка длинного ответа на части и отправка
                messages = split_message(answer_html, MAX_MESSAGE_LENGTH)
                for msg in messages:
                    await query.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
            elif mode == 'file':
                # Сохранение ответа в .md файл
                file_path = save_md_file(answer, user_id)
                if file_path:
                    # Отправка файла пользователю
                    try:
                        with open(file_path, 'rb') as f:
                            await query.message.reply_document(
                                document=InputFile(f),
                                filename=os.path.basename(file_path),
                                caption="твой ответ модели в формате markdown.",
                                reply_markup=get_main_buttons(user_id, current_model_id)
                            )
                        
                        # Удаление файла после отправки
                        os.remove(file_path)
                    except Exception as e:
                        logger.error(f"Ошибка при отправке файла: {e}")
                        await query.message.reply_text('не удалось отправить файл. попробуйте позже.', reply_markup=get_main_buttons(user_id, current_model_id))
                else:
                    await query.message.reply_text('не удалось создать файл с ответом.', reply_markup=get_main_buttons(user_id, current_model_id))
        else:
            logger.error("Неправильная структура ответа от API.")
            await query.message.reply_text('api вернул непредвиденный ответ. попробуй позже.', reply_markup=get_main_buttons(user_id, current_model_id))
    elif selected_model_type == 'image':
        await query.message.reply_text('функция "продолжить" не поддерживается для выбранной модели.', reply_markup=get_main_buttons(user_id, current_model_id))
    else:
        await query.message.reply_text('неизвестный тип модели.', reply_markup=get_main_buttons(user_id, current_model_id))

# Обработчик текстовых сообщений
async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь отправил сообщение: {user_id}")
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return

    user_mode = user_mode_store.get(user_id, 'message')

    chat_id = update.effective_chat.id
    user_message = update.message.text

    # Получаем текущую модель и чат слота
    user_active_slots = active_chat_slot.get(user_id, {})
    if not user_active_slots:
        await update.message.reply_text('модель не выбрана. используйте /start для выбора модели.', reply_markup=get_main_buttons(user_id))
        return

    # Для простоты предполагаем, что пользователь взаимодействует с одной моделью за раз
    # Если необходимо поддерживать несколько моделей одновременно, потребуется дополнительная логика
    # Здесь берем первую выбранную модель
    current_model_id = next(iter(user_active_slots))
    current_chat_slot = user_active_slots[current_model_id]

    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai')
    selected_model_type = model_info.get('type', 'text')

    # Проверка поддержки текстового режима моделью
    if selected_model_type not in ['text', 'multimodal', 'image']:
        await update.message.reply_text('выбранная модель не поддерживает этот тип ввода.', reply_markup=get_main_buttons(user_id, current_model_id))
        return

    # Проверка доступа к модели
    if current_model_id in RESTRICTED_MODELS and user_id not in AUTHORIZED_MODEL_USERS:
        await update.message.reply_text('вам нельзя использовать эту модель.', reply_markup=get_main_buttons(user_id, current_model_id))
        return

    # Инициализация контекста для текстовой или мультимодальной модели, если ещё не инициализирована
    if selected_model_type in ['text', 'multimodal'] and current_chat_slot not in context_store.get(user_id, {}).get(current_model_id, {}):
        system_instruction = SYSTEM_INSTRUCTION_TEXT
        if provider == 'openai':
            system_instruction = "You are a helpful assistant."  # Можно изменить по необходимости
        context_store[user_id][current_model_id][current_chat_slot] = load_chat_context(user_id, current_model_id, current_chat_slot, system_instruction)
    
    # Добавление сообщения пользователя в контекст для текстовых и мультимодальных моделей
    if selected_model_type in ['text', 'multimodal']:
        context_store[user_id][current_model_id][current_chat_slot].append({"role": "user", "content": user_message})

        # Подготовка данных для API с учетом контекста
        data = {
            "model": current_model_id,
            "messages": context_store[user_id][current_model_id][current_chat_slot]
        }

        if provider == 'cablyai':
            response_data = send_cablyai_request(current_model_id, data['messages'])
        elif provider == 'openai':
            response = send_openai_request(current_model_id, messages=data['messages'])
            response_data = response.to_dict() if response else None
        else:
            response_data = None

        if response_data and response_data.get('choices'):
            answer = response_data["choices"][0].get('message', {}).get('content', 'что-то пошло не так. мяу :(')
            
            if user_mode == 'message':
                # Преобразование Markdown в HTML
                answer_html = markdown_to_html(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])
                
                # Разбивка длинного ответа на части и отправка
                messages = split_message(answer_html, MAX_MESSAGE_LENGTH)
                for msg in messages:
                    await update.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
            elif user_mode == 'file':
                # Сохранение ответа в .md файл
                file_path = save_md_file(answer, user_id)
                if file_path:
                    # Отправка файла пользователю
                    try:
                        with open(file_path, 'rb') as f:
                            await update.message.reply_document(
                                document=InputFile(f),
                                filename=os.path.basename(file_path),
                                caption="твой ответ модели в формате markdown.",
                                reply_markup=get_main_buttons(user_id, current_model_id)
                            )
                        
                        # Удаление файла после отправки
                        os.remove(file_path)
                    except Exception as e:
                        logger.error(f"Ошибка при отправке файла: {e}")
                        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))
        else:
            logger.error("Неправильная структура ответа от API.")
            await update.message.reply_text('api вернул непредвиденный ответ. попробуй позже.', reply_markup=get_main_buttons(user_id, current_model_id))
    elif selected_model_type == 'image':
        # Обработка для моделей типа 'image'
        if is_image_supported(current_model_id):
            # Обработка изображения через OpenAI API (пример для генерации изображения по описанию)
            # Здесь предполагается, что пользователь отправляет изображение для анализа или иного использования
            # В OpenAI API есть разные методы работы с изображениями, но для этой реализации будем генерировать изображение по запросу
            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))
    else:
        await update.message.reply_text('неизвестный тип модели.', reply_markup=get_main_buttons(user_id, current_model_id))

# Обработчик изображений
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь отправил изображение: {user_id}")
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return

    user_mode = user_mode_store.get(user_id, 'message')

    chat_id = update.effective_chat.id
    photo = update.message.photo[-1]  # Берем наибольшее разрешение изображения
    file = await photo.get_file()
    file_path = os.path.join('downloads', 'photos', f"{user_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}.jpg")
    
    # Скачиваем изображение
    try:
        if not os.path.exists('downloads/photos'):
            os.makedirs('downloads/photos')
        await file.download_to_drive(file_path)
    except Exception as e:
        logger.error(f"Ошибка при скачивании изображения: {e}")
        await update.message.reply_text('не удалось скачать изображение. попробуй позже.', reply_markup=get_main_buttons(user_id))
        return

    # Получаем текущую модель и чат слота
    user_active_slots = active_chat_slot.get(user_id, {})
    if not user_active_slots:
        await update.message.reply_text('модель не выбрана. используйте /start для выбора модели.', reply_markup=get_main_buttons(user_id))
        return

    current_model_id = next(iter(user_active_slots))
    current_chat_slot = user_active_slots[current_model_id]

    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai')
    selected_model_type = model_info.get('type', 'text')

    # Проверка поддержки изображений
    if not is_image_supported(current_model_id):
        await update.message.reply_text('выбранная модель не поддерживает обработку изображений.', reply_markup=get_main_buttons(user_id, current_model_id))
        return

    # Отправка изображения через OpenAI API
    try:
        with open(file_path, 'rb') as image_file:
            response = openai.Image.create(
                file=image_file,
                model=current_model_id.replace("openai-", ""),  # Убираем префикс для соответствия OpenAI API
                prompt="Analyze the provided image and provide a detailed response."  # Пример запроса
            )
        # Обработка ответа OpenAI API
        if response and response.get('choices'):
            answer = response["choices"][0].get('text', 'что-то пошло не так. мяу :(')
            if user_mode == 'message':
                # Преобразование Markdown в HTML
                answer_html = markdown_to_html(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])
                
                # Разбивка длинного ответа на части и отправка
                messages = split_message(answer_html, MAX_MESSAGE_LENGTH)
                for msg in messages:
                    await update.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
            elif user_mode == 'file':
                # Сохранение ответа в .md файл
                file_path_md = save_md_file(answer, user_id)
                if file_path_md:
                    # Отправка файла пользователю
                    try:
                        with open(file_path_md, 'rb') as f:
                            await update.message.reply_document(
                                document=InputFile(f),
                                filename=os.path.basename(file_path_md),
                                caption="твой ответ модели в формате markdown.",
                                reply_markup=get_main_buttons(user_id, current_model_id)
                            )
                        # Удаление файла после отправки
                        os.remove(file_path_md)
                    except Exception as e:
                        logger.error(f"Ошибка при отправке файла: {e}")
                        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))
    except Exception as e:
        logger.error(f"Ошибка при обработке изображения через OpenAI API: {e}")
        await update.message.reply_text('произошла ошибка при обработке изображения. попробуйте позже.', reply_markup=get_main_buttons(user_id, current_model_id))
    finally:
        # Удаление скачанного изображения
        if os.path.exists(file_path):
            os.remove(file_path)

# Обработчик документов (файлов)
async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь попытался отправить файл: {user_id}")
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return

    user_mode = user_mode_store.get(user_id, 'message')

    chat_id = update.effective_chat.id
    document = update.message.document
    file = await document.get_file()
    filename = document.file_name.lower()  # Приводим к нижнему регистру для надёжной проверки

    # Проверка, является ли файл .py
    if filename.endswith('.py'):
        # Обработка как текстовый файл
        try:
            # Создаём директорию для скачивания, если её нет
            download_path = os.path.join('downloads', 'python_files')
            if not os.path.exists(download_path):
                os.makedirs(download_path)

            # Скачиваем файл
            file_path = os.path.join(download_path, document.file_name)
            await 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('не удалось скачать или прочитать файл. попробуй позже.', reply_markup=get_main_buttons(user_id))
            return

        # Получаем текущую модель и активный чат слота
        user_active_slots = active_chat_slot.get(user_id, {})
        if not user_active_slots:
            await update.message.reply_text('модель не выбрана. используйте /start для выбора модели.', reply_markup=get_main_buttons(user_id))
            return

        # Берем первую выбранную модель
        current_model_id = next(iter(user_active_slots))
        current_chat_slot = user_active_slots[current_model_id]

        model_info = model_id_to_info.get(current_model_id, {})
        provider = model_info.get('provider', 'cablyai')
        selected_model_type = model_info.get('type', 'text')

        # Проверка доступа к модели
        if current_model_id in RESTRICTED_MODELS and user_id not in AUTHORIZED_MODEL_USERS:
            logger.warning(f"Пользователь {user_id} пытался использовать ограниченную модель {current_model_id}")
            await update.message.reply_text('вам нельзя использовать эту модель.', reply_markup=get_main_buttons(user_id, current_model_id))
            return

        # Проверяем, поддерживает ли модель текстовый ввод
        if selected_model_type not in ['text', 'multimodal', 'image']:
            await update.message.reply_text('выбранная модель не поддерживает обработку текстовых файлов.', reply_markup=get_main_buttons(user_id, current_model_id))
            return

        # Инициализация контекста для текстовой или мультимодальной модели, если ещё не инициализирована
        if selected_model_type in ['text', 'multimodal'] and current_chat_slot not in context_store.get(user_id, {}).get(current_model_id, {}):
            system_instruction = SYSTEM_INSTRUCTION_TEXT
            if provider == 'openai':
                system_instruction = "You are a helpful assistant."
            context_store[user_id][current_model_id][current_chat_slot] = load_chat_context(user_id, current_model_id, current_chat_slot, system_instruction)

        # Добавление сообщения пользователя в контекст для текстовых и мультимодальных моделей
        if selected_model_type in ['text', 'multimodal']:
            context_store[user_id][current_model_id][current_chat_slot].append({"role": "user", "content": content})

            # Подготовка данных для API с учетом контекста
            data = {
                "model": current_model_id,
                "messages": context_store[user_id][current_model_id][current_chat_slot]
            }

            if provider == 'cablyai':
                response_data = send_cablyai_request(current_model_id, data['messages'])
            elif provider == 'openai':
                response = send_openai_request(current_model_id, messages=data['messages'])
                response_data = response.to_dict() if response else None
            else:
                response_data = None

            if response_data and response_data.get('choices'):
                answer = response_data["choices"][0].get('message', {}).get('content', 'что-то пошло не так. мяу :(')
                
                if user_mode == 'message':
                    # Преобразование Markdown в HTML
                    answer_html = markdown_to_html(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])
                    
                    # Разбивка длинного ответа на части и отправка
                    messages = split_message(answer_html, MAX_MESSAGE_LENGTH)
                    for msg in messages:
                        await update.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
                elif user_mode == 'file':
                    # Сохранение ответа в .md файл
                    file_path_md = save_md_file(answer, user_id)
                    if file_path_md:
                        # Отправка файла пользователю
                        try:
                            with open(file_path_md, 'rb') as f:
                                await update.message.reply_document(
                                    document=InputFile(f),
                                    filename=os.path.basename(file_path_md),
                                    caption="ваш ответ модели в формате markdown.",
                                    reply_markup=get_main_buttons(user_id, current_model_id)
                                )
                            
                            # Удаление файла после отправки
                            os.remove(file_path_md)
                        except Exception as e:
                            logger.error(f"Ошибка при отправке файла: {e}")
                            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))
            else:
                logger.error("Неправильная структура ответа от API.")
                await update.message.reply_text('api вернул непредвиденный ответ. попробуй позже.', reply_markup=get_main_buttons(user_id, current_model_id))
    else:
        logger.error(f"Неподдерживаемый тип файла: {filename}")
        await update.message.reply_text('поддерживаются только .py файлы.', reply_markup=get_main_buttons(user_id))

# Обработчик фотографий
async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user_id = update.effective_user.id
    if user_id not in AUTHORIZED_USER_IDS:
        logger.info(f"Неавторизованный пользователь отправил изображение: {user_id}")
        await update.message.reply_text('тебе сюда мяукать нельзя.')
        return

    user_mode = user_mode_store.get(user_id, 'message')

    chat_id = update.effective_chat.id
    photo = update.message.photo[-1]  # Берем наибольшее разрешение изображения
    file = await photo.get_file()
    file_path = os.path.join('downloads', 'photos', f"{user_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}.jpg")
    
    # Скачиваем изображение
    try:
        if not os.path.exists('downloads/photos'):
            os.makedirs('downloads/photos')
        await file.download_to_drive(file_path)
    except Exception as e:
        logger.error(f"Ошибка при скачивании изображения: {e}")
        await update.message.reply_text('не удалось скачать изображение. попробуй позже.', reply_markup=get_main_buttons(user_id))
        return

    # Получаем текущую модель и чат слота
    user_active_slots = active_chat_slot.get(user_id, {})
    if not user_active_slots:
        await update.message.reply_text('модель не выбрана. используйте /start для выбора модели.', reply_markup=get_main_buttons(user_id))
        return

    # Берем первую выбранную модель
    current_model_id = next(iter(user_active_slots))
    current_chat_slot = user_active_slots[current_model_id]

    model_info = model_id_to_info.get(current_model_id, {})
    provider = model_info.get('provider', 'cablyai')
    selected_model_type = model_info.get('type', 'text')

    # Проверка поддержки изображений
    if not is_image_supported(current_model_id):
        await update.message.reply_text('выбранная модель не поддерживает обработку изображений.', reply_markup=get_main_buttons(user_id, current_model_id))
        return

    # Обработка изображения
    try:
        with open(file_path, 'rb') as image_file:
            if provider == 'openai':
                response = openai.Image.create(
                    file=image_file,
                    prompt="Analyze the provided image and provide a detailed response.",
                    model=current_model_id.replace("openai-", "")  # Убираем префикс для соответствия OpenAI API
                )
                response_data = response.to_dict()
            else:
                response_data = send_cablyai_request(current_model_id, [{"role": "user", "content": "Обработай это изображение."}])
    except Exception as e:
        logger.error(f"Ошибка при обработке изображения через API: {e}")
        await update.message.reply_text('произошла ошибка при обработке изображения. попробуйте позже.', reply_markup=get_main_buttons(user_id, current_model_id))
        return
    finally:
        # Удаление скачанного изображения
        if os.path.exists(file_path):
            os.remove(file_path)

    # Обработка ответа
    if response_data and response_data.get('choices'):
        if provider == 'openai':
            answer = response_data["choices"][0].get('text', 'что-то пошло не так. мяу :(')
        else:
            answer = response_data["choices"][0].get('message', {}).get('content', 'что-то пошло не так. мяу :(')
        
        if user_mode == 'message':
            # Преобразование Markdown в HTML
            answer_html = markdown_to_html(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])
            
            # Разбивка длинного ответа на части и отправка
            messages = split_message(answer_html, MAX_MESSAGE_LENGTH)
            for msg in messages:
                await update.message.reply_text(msg, reply_markup=get_main_buttons(user_id, current_model_id))
        elif user_mode == 'file':
            # Сохранение ответа в .md файл
            file_path_md = save_md_file(answer, user_id)
            if file_path_md:
                # Отправка файла пользователю
                try:
                    with open(file_path_md, 'rb') as f:
                        await update.message.reply_document(
                            document=InputFile(f),
                            filename=os.path.basename(file_path_md),
                            caption="твой ответ модели в формате markdown.",
                            reply_markup=get_main_buttons(user_id, current_model_id)
                        )
                    
                    # Удаление файла после отправки
                    os.remove(file_path_md)
                except Exception as e:
                    logger.error(f"Ошибка при отправке файла: {e}")
                    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))
    else:
        logger.error("Неправильная структура ответа от API.")
        await update.message.reply_text('api вернул непредвиденный ответ. попробуй позже.', reply_markup=get_main_buttons(user_id, current_model_id))

# Обработчик ошибок
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Ловит все необработанные ошибки."""
    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
            # Получаем текущую модель и активный чат слота
            user_active_slots = active_chat_slot.get(user_id, {})
            if user_active_slots:
                current_model_id = next(iter(user_active_slots))
                await update.effective_message.reply_text('произошла ошибка. пожалуйста, попробуйте позже.', reply_markup=get_main_buttons(user_id, current_model_id))
            else:
                await update.effective_message.reply_text('произошла ошибка. пожалуйста, попробуйте позже.', reply_markup=get_main_buttons(user_id))
        except TelegramError as e:
            logger.error(f"Не удалось отправить сообщение об ошибке пользователю: {e}")

# Основная функция запуска бота
def main():
    # Загрузка конфигурации уже произведена выше
    TELEGRAM_TOKEN = config.get('telegram_token')
    if not TELEGRAM_TOKEN:
        logger.error("Токен бота не указан в config.json.")
        exit(1)
    
    # Создание директории для чатов, если её нет
    chats_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'chats')
    if not os.path.exists(chats_dir):
        os.makedirs(chats_dir)
    
    # Создание директории для загрузок фотографий, если её нет
    downloads_photos_dir = os.path.join('downloads', 'photos')
    if not os.path.exists(downloads_photos_dir):
        os.makedirs(downloads_photos_dir)

    # Создание экземпляра приложения с установкой ParseMode.HTML по умолчанию
    application = ApplicationBuilder().token(TELEGRAM_TOKEN).defaults(Defaults(parse_mode=ParseMode.HTML)).build()

    # Обработчики команд
    application.add_handler(CommandHandler("start", start))
    application.add_handler(CommandHandler("setmode", setmode))
    application.add_handler(CommandHandler("get_mode", get_mode))
    application.add_handler(CommandHandler("instructions", instructions))
    application.add_handler(CommandHandler("models", models_command))  # Добавлена команда /models
    
    # Обработчики CallbackQuery сначала
    # Обработчик выборки моделей и ограниченных моделей
    application.add_handler(CallbackQueryHandler(select_model, pattern='^model_select:.+$|^restricted:.+$'))
    
    # Обработчик выбора слота чата
    application.add_handler(CallbackQueryHandler(select_chat_slot, pattern='^chat_slot:\d+:.+$'))
    
    # Обработчики CallbackQuery для основных кнопок
    application.add_handler(CallbackQueryHandler(change_model_callback, pattern='^change_model$'))
    application.add_handler(CallbackQueryHandler(change_chat_slot_callback, pattern='^change_chat_slot$'))
    application.add_handler(CallbackQueryHandler(clear_context_callback, pattern='^clear_context$'))
    application.add_handler(CallbackQueryHandler(choose_chat_model, pattern='^choose_chat_model:.+$'))
    application.add_handler(CallbackQueryHandler(clear_context_model, pattern='^clear_context_model:.+$'))
    application.add_handler(CallbackQueryHandler(continue_response, pattern='^continue_response$'))
    
    # Обработчики сообщений
    # Обработка текстовых сообщений
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))
    
    # Обработка фотографий
    application.add_handler(MessageHandler(filters.PHOTO, handle_photo))
    
    # Обработка документов (файлов)
    application.add_handler(MessageHandler(filters.Document.ALL, handle_document))
    
    # Обработчик ошибок
    application.add_error_handler(error_handler)

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

if __name__ == '__main__':
    main()