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

# Функция для загрузки конфигурации
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)

# Загрузка конфигурации
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__)

# Ваши API-эндпоинты
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 = {}
model_store = {}
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 — модель для генерации изображений по текстовому описанию. контекста не имеет, работая лишь с текущим заданным запросом."
}

# Функция для разбивки длинного сообщения на части
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']
        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=model_id)])
    return InlineKeyboardMarkup(buttons)

# Функция для создания основных кнопок: 'сменить модель' и 'продолжить'
def get_main_buttons():
    keyboard = [
        [
            InlineKeyboardButton("сменить модель", callback_data='change_model'),
            InlineKeyboardButton("продолжить", callback_data='continue_response')
        ]
    ]
    return InlineKeyboardMarkup(keyboard)

# Определение get_menu_buttons как алиаса для get_main_buttons
def get_menu_buttons():
    return get_main_buttons()

# Функция для определения, является ли текст русским
def is_russian(text):
    return bool(re.search('[а-яА-Я]', text))

# Функция для перевода текста с помощью модели GPT-4
def translate_to_english(original_text):
    translation_prompt = f"переведи следующий текст на английский язык и верни только переведенный текст:\n\n{original_text}"
    
    translation_data = {
        "model": "chatgpt-4o-latest",
        "messages": [
            {"role": "system", "content": "you are a helpful assistant that translates russian text to english."},
            {"role": "user", "content": translation_prompt}
        ]
    }
    
    try:
        response = requests.post(API_URL_TEXT, headers=API_HEADERS, json=translation_data)
        response.raise_for_status()
        response_data = response.json()
        
        if "choices" in response_data and len(response_data["choices"]) > 0:
            translated_text = response_data["choices"][0].get('message', {}).get('content', '').strip()
            return translated_text
        else:
            logger.error("Неверная структура ответа от модели при переводе.")
            return None
    except requests.exceptions.RequestException as e:
        logger.error(f"Ошибка при переводе текста: {e}")
        return None

# Функция для преобразования 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

# Обработчик команды /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> - продолжить ответ модели.\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:
    """
    Отправляет пользователю список доступных моделей с описаниями.
    """
    models = config.get('models', {})
    if not models:
        await update.message.reply_text('нет доступных моделей.', reply_markup=get_main_buttons())
        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 chat_id not in context_store:
        context_store[chat_id] = {}
    
    # Если модель ещё не выбрана, устанавливаем модель по умолчанию
    if chat_id not in model_store:
        if model_id_to_info:
            default_model_id = next(iter(model_id_to_info))
            model_store[chat_id] = default_model_id
            model_info = model_id_to_info[default_model_id]
            if model_info.get('type') in ['text', 'multimodal']:
                if default_model_id not in context_store[chat_id]:
                    # Только для текстовых и мультимодальных моделей инициализируем контекст
                    if model_info.get('type') in ['text', 'multimodal']:
                        context_store[chat_id][default_model_id] = [{"role": "system", "content": SYSTEM_INSTRUCTION_TEXT}]
        else:
            await update.message.reply_text('нет доступных моделей для использования.', reply_markup=get_main_buttons())
            return
    
    # Кнопки для выбора модели
    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()
        )

# Обработчик выбора модели через кнопки
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
    else:
        model_id = selected_model_data

    model_info = model_id_to_info.get(model_id)

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

    chat_id = update.effective_chat.id
    model_store[chat_id] = model_id

    model_type = model_info.get('type', 'text')

    # Инициализация контекста для выбранной модели, только если это текстовая или мультимодальная модель
    if model_type in ['text', 'multimodal']:
        if model_id not in context_store.get(chat_id, {}):
            context_store[chat_id][model_id] = [{"role": "system", "content": SYSTEM_INSTRUCTION_TEXT}]
    else:
        # Для других типов моделей контекст не инициализируется или очищается
        if chat_id in context_store and model_id in context_store[chat_id]:
            del context_store[chat_id][model_id]

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

# Обработчик текстовых сообщений
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

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

    # Проверка, нажата ли кнопка "меню"
    if user_message.lower() == "меню":
        await show_menu(update, context)
        return

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

    if chat_id not in model_store:
        await update.message.reply_text('модель не выбрана. используется модель по умолчанию.')
        if model_id_to_info:
            default_model_id = next(iter(model_id_to_info))
            model_store[chat_id] = default_model_id
            model_info = model_id_to_info[default_model_id]
            if model_info.get('type') in ['text', 'multimodal']:
                context_store[chat_id].setdefault(default_model_id, []).append({"role": "system", "content": SYSTEM_INSTRUCTION_TEXT})
        else:
            await update.message.reply_text('нет доступных моделей для использования.', reply_markup=get_main_buttons())
            return

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

    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_menu_buttons())
        return

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

    # Инициализация контекста для текстовой или мультимодальной модели, если ещё не инициализирована
    if selected_model_type in ['text', 'multimodal'] and selected_model_id not in context_store.get(chat_id, {}):
        context_store[chat_id][selected_model_id] = [{"role": "system", "content": SYSTEM_INSTRUCTION_TEXT}]
    
    # Добавление сообщения пользователя в контекст для текстовых и мультимодальных моделей
    if selected_model_type in ['text', 'multimodal']:
        context_store[chat_id][selected_model_id].append({"role": "user", "content": user_message})

        # Подготовка данных для API с учетом контекста
        data = {
            "model": selected_model_id,
            "messages": context_store[chat_id][selected_model_id]
        }
        api_url = API_URL_TEXT
    elif selected_model_type == 'image':
        # Обработка для моделей типа 'image'
        if is_russian(user_message):
            # Сообщаем пользователю о переводе
            await update.message.reply_text('твой запрос содержит русский язык. генерация картинки будет идти чуть дольше - другой модели нужно перевести текст на английский для более корректного понимания моделью твоего запроса. если тебя не устроила полученная картинка - ты всегда можешь сделать ещё запрос.')

            # Переводим запрос на английский
            translated_prompt = translate_to_english(user_message)
            if not translated_prompt:
                await update.message.reply_text('не удалось перевести запрос. попробуй позже.', reply_markup=get_main_buttons())
                return

            # Используем переведенный запрос для генерации изображения
            data = {
                "model": selected_model_id,
                "prompt": translated_prompt  # Не экранируем, так как это отправка API
            }
        else:
            # Подготовка данных для API без перевода
            data = {
                "model": selected_model_id,
                "prompt": user_message  # Не экранируем, так как это отправка API
            }
        api_url = API_URL_IMAGE
    else:
        await update.message.reply_text('неизвестный тип модели.', reply_markup=get_main_buttons())
        return