telegram-cli-bot/bot/handlers/callbacks.py

879 lines
42 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Обработчик callback-запросов от меню."""
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes
from bot.config import config, state_manager, server_manager, menu_builder
from bot.utils.decorators import check_access
from bot.services.command_executor import execute_cli_command
from bot.models.user_state import (
AI_PRESET_OFF,
AI_PRESET_QWEN,
AI_PRESET_GIGA_AUTO,
AI_PRESET_GIGA_LITE,
AI_PRESET_GIGA_PRO,
AI_PRESET_GIGA_MAX,
AI_PRESET_OPENCODE,
)
from memory_system import memory_manager, get_user_profile_summary
logger = logging.getLogger(__name__)
async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка нажатий на кнопки меню."""
query = update.callback_query
user_id = query.from_user.id
state = state_manager.get(user_id)
await query.answer()
callback = query.data
logger.info(f"Callback: {callback} от пользователя {user_id}")
# Обработка навигации
if callback == "main":
state.current_menu = "main"
# Проверяем режим чата с ИИ для обновления текста кнопки
ai_status = "✅ ВКЛ" if state.ai_chat_mode else "❌ ВЫКЛ"
await query.edit_message_text(
f"🏠 *Главное меню*\n\n"
f"💬 *Чат с ИИ:* {ai_status}",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id, state=state)
)
elif callback == "ai_presets":
# Открываем меню AI-пресетов
state = state_manager.get(user_id)
from bot.handlers.ai_presets import ai_presets_command
# Создаём фейковое сообщение для совместимости
class FakeMessage:
async def reply_text(self, text, parse_mode=None, reply_markup=None):
# Вместо отправки сообщения редактируем callback
await query.edit_message_text(text, parse_mode=parse_mode, reply_markup=reply_markup)
return None
fake_update = type('FakeUpdate', (), {'message': FakeMessage(), 'effective_user': query.from_user})()
await ai_presets_command(fake_update, context)
return
# Обработчики подменю AI-пресетов
elif callback == "opencode_submenu":
# Подменю выбора моделей Opencode из AI-пресетов
state = state_manager.get(user_id)
current_model = state.opencode_model
state.ai_preset = AI_PRESET_OPENCODE
state.current_ai_provider = "opencode"
keyboard = [
[InlineKeyboardButton(f"{'' if current_model == 'minimax' else ''} ⚡ minimax", callback_data="opencode_model_minimax")],
[InlineKeyboardButton(f"{'' if current_model == 'big_pickle' else ''} 🗃️ big-pickle", callback_data="opencode_model_big_pickle")],
[InlineKeyboardButton(f"{'' if current_model == 'gpt5' else ''} 🔬 gpt-5-nano", callback_data="opencode_model_gpt5")],
[InlineKeyboardButton("⬅️ Назад к AI-пресетам", callback_data="ai_presets")],
]
model_descriptions = {
"minimax": "Быстрая, бесплатная модель. Хорошо справляется с простыми задачами.",
"big_pickle": "Большая бесплатная модель. Лучше для сложных задач.",
"gpt5": "Самая мощная бесплатная модель. Требует больше времени."
}
await query.edit_message_text(
f"📡 **Выбор модели Opencode**\n\n"
f"Текущая модель: **{current_model}**\n\n"
f" Описание моделей:\n"
f"• ⚡ **minimax** — {model_descriptions['minimax']}\n"
f"• 🗃️ **big-pickle** — {model_descriptions['big_pickle']}\n"
f"• 🔬 **gpt-5-nano** — {model_descriptions['gpt5']}\n\n"
f"Выберите модель для использования:",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback == "gigachat_submenu":
# Подменю выбора моделей GigaChat из AI-пресетов
state = state_manager.get(user_id)
current_model = state.gigachat_model
keyboard = [
[InlineKeyboardButton(f"{'' if current_model == 'lite' else ''} 📱 GigaChat Lite", callback_data="ai_preset_giga_lite")],
[InlineKeyboardButton(f"{'' if current_model == 'pro' else ''} 🚀 GigaChat Pro", callback_data="ai_preset_giga_pro")],
[InlineKeyboardButton(f"{'' if current_model == 'max' else ''} 💎 GigaChat Max", callback_data="ai_preset_giga_max")],
[InlineKeyboardButton("⬅️ Назад к AI-пресетам", callback_data="ai_presets")],
]
model_descriptions = {
"lite": "Быстрая и экономичная модель для простых задач",
"pro": "Баланс скорости и качества для большинства задач",
"max": "Самая мощная модель для сложных задач"
}
await query.edit_message_text(
f"🧠 **Выбор модели GigaChat**\n\n"
f"Текущая модель: **{current_model.upper()}**\n\n"
f" Описание моделей:\n"
f"• 📱 **Lite** — {model_descriptions['lite']}\n"
f"• 🚀 **Pro** — {model_descriptions['pro']}\n"
f"• 💎 **Max** — {model_descriptions['max']}\n\n"
f"Выберите модель для использования:",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback.startswith("continue_output_"):
# Пользователь нажал "Продолжить"
parts = callback.replace("continue_output_", "").split("_")
remaining = int(parts[0])
next_index = int(parts[1]) if len(parts) > 1 else 0
state = state_manager.get(user_id)
logger.info(f"callback continue_output: remaining={remaining}, next_index={next_index}, user_id={user_id}")
# Сначала отвечаем на callback
await query.answer()
# Удаляем сообщение с кнопками
try:
if state.output_wait_message:
await state.output_wait_message.delete()
except:
pass
# Продолжаем отправку сообщений
if state.output_text:
from bot.utils.formatters import send_long_message
# Создаём фейковый update для совместимости
class FakeMessage:
async def reply_text(self, text, parse_mode=None, reply_markup=None):
return await query.message.reply_text(text, parse_mode=parse_mode, reply_markup=reply_markup)
fake_update = type('FakeUpdate', (), {
'message': FakeMessage(),
'effective_user': query.from_user
})()
# Продолжаем отправку
has_more = await send_long_message(
fake_update,
state.output_text,
parse_mode=state.output_parse_mode,
start_from=next_index
)
# Если ещё есть сообщения — сохраняем состояние
if has_more:
logger.info(f"Продолжение отправлено, ещё есть пауза")
else:
logger.info(f"Все сообщения отправлены")
state.output_text = None
else:
logger.warning(f"output_text не найден в состоянии")
return
elif callback == "cancel_output":
# Пользователь нажал "Отменить"
logger.info(f"callback cancel_output: user_id={user_id}")
state = state_manager.get(user_id)
# Сначала отвечаем на callback
await query.answer()
# Удаляем сообщение с кнопками
try:
if state.output_wait_message:
await state.output_wait_message.delete()
except:
pass
# Очищаем состояние
state.waiting_for_output_control = False
state.output_remaining = None
state.output_wait_message = None
state.output_text = None
state.output_next_index = None
await query.message.reply_text("❌ Вывод отменён пользователем")
return
elif callback == "preset_menu":
state.current_menu = "preset"
await query.edit_message_text(
"📋 *Предустановленные команды*",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("preset")
)
elif callback == "fs_menu":
await query.edit_message_text(
"📁 *Файловая система*",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("fs")
)
elif callback == "search_menu":
await query.edit_message_text(
"🔍 *Поиск*",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("search")
)
elif callback == "system_menu":
await query.edit_message_text(
"📊 *Система*",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("system")
)
elif callback == "network_menu":
await query.edit_message_text(
"🌐 *Сеть*",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("network")
)
elif callback == "opencode_models_menu":
# Меню выбора моделей Opencode в предустановленных командах
state = state_manager.get(user_id)
current_model = state.opencode_model
state.current_ai_provider = "opencode"
keyboard = [
[InlineKeyboardButton(f"{'' if current_model == 'minimax' else ''} ⚡ minimax", callback_data="opencode_model_minimax")],
[InlineKeyboardButton(f"{'' if current_model == 'big_pickle' else ''} 🗃️ big-pickle", callback_data="opencode_model_big_pickle")],
[InlineKeyboardButton(f"{'' if current_model == 'gpt5' else ''} 🔬 gpt-5-nano", callback_data="opencode_model_gpt5")],
[InlineKeyboardButton("⬅️ Назад", callback_data="preset_menu")],
]
model_descriptions = {
"minimax": "Быстрая, бесплатная модель",
"big_pickle": "Большая бесплатная модель",
"gpt5": "Самая мощная бесплатная модель"
}
await query.edit_message_text(
f"🤖 **AI модели Opencode**\n\n"
f"Текущая модель: **{current_model}**\n\n"
f" Описание:\n"
f"• ⚡ **minimax** — {model_descriptions['minimax']}\n"
f"• 🗃️ **big-pickle** — {model_descriptions['big_pickle']}\n"
f"• 🔬 **gpt-5-nano** — {model_descriptions['gpt5']}\n\n"
f"Выберите модель:",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback == "server_menu":
# Сброс состояния редактирования/добавления сервера
state.waiting_for_input = False
state.input_type = None
state.editing_server = None
state.context.clear()
# Динамическое обновление меню серверов с кнопками управления
servers = server_manager.list_servers()
keyboard = []
for srv in servers:
# Кнопка выбора сервера + кнопка управления (для не-local)
row = [InlineKeyboardButton(
srv.display_name,
callback_data=f"server_select_{srv.name}"
)]
if srv.name != "local":
row.append(InlineKeyboardButton(
"⚙️",
callback_data=f"server_manage_{srv.name}"
))
keyboard.append(row)
keyboard.append([
InlineKeyboardButton(" Добавить", callback_data="server_add"),
InlineKeyboardButton("⬅️ Назад", callback_data="main")
])
state.current_menu = "server"
await query.edit_message_text(
"🖥️ *Управление серверами*\n\n"
"Выберите сервер для подключения или добавьте новый.\n"
"⚙️ — редактировать/удалить сервер",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback == "server_add":
state.waiting_for_input = True
state.input_type = "add_server_name"
state.context["new_server"] = {}
await query.edit_message_text(
" *Добавление сервера*\n\n"
"Введите *имя сервера* (латиница, без пробелов):\n"
"Пример: `web-prod`, `db-backup`",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("❌ Отмена", callback_data="server_menu")
]])
)
elif callback.startswith("server_manage_"):
server_name = callback.replace("server_manage_", "")
server = server_manager.get(server_name)
if server and server_name != "local":
state.editing_server = server_name
await query.edit_message_text(
f"⚙️ *Управление сервером*\n\n"
f"{server.display_name}\n"
f"📍 `{server.description}`\n"
f"🏷️ Теги: `{','.join(server.tags) if server.tags else 'нет'}`\n\n"
f"Выберите действие:",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton("✏️ Редактировать", callback_data=f"server_edit_{server_name}")],
[InlineKeyboardButton("🗑️ Удалить", callback_data=f"server_delete_{server_name}")],
[InlineKeyboardButton("⬅️ Назад", callback_data="server_menu")]
])
)
else:
await query.edit_message_text(
f"❌ *Сервер не найден*\n\n"
f"Сервер `{server_name}` отсутствует в конфигурации.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("server")
)
elif callback.startswith("server_edit_"):
server_name = callback.replace("server_edit_", "")
server = server_manager.get(server_name)
if server and server_name != "local":
state.editing_server = server_name
state.waiting_for_input = True
state.input_type = "edit_server_field"
password_status = "установлен" if server.password else "не установлен"
await query.edit_message_text(
f"✏️ *Редактирование сервера: {server_name}*\n\n"
f"Текущие значения:\n"
f"• Host: `{server.host}`\n"
f"• Port: `{server.port}`\n"
f"• User: `{server.user}`\n"
f"• Tags: `{','.join(server.tags) if server.tags else 'нет'}`\n"
f"• Password: {password_status}\n\n"
f"Введите номер поля для изменения:\n"
f"1 — Host\n"
f"2 — Port\n"
f"3 — User\n"
f"4 — Tags\n"
f"5 — Password",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("❌ Отмена", callback_data="server_menu")
]])
)
else:
await query.edit_message_text(
"❌ Ошибка: сервер не найден",
reply_markup=menu_builder.get_keyboard("server")
)
elif callback.startswith("server_delete_"):
server_name = callback.replace("server_delete_", "")
server = server_manager.get(server_name)
if server and server_name != "local":
# Удаляем сразу с подтверждением
if server_manager.delete_server(server_name):
await query.edit_message_text(
f"🗑️ *Сервер удалён*\n\n"
f"Сервер `{server_name}` успешно удалён из конфигурации.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("server")
)
else:
await query.edit_message_text(
"❌ Ошибка при удалении сервера",
reply_markup=menu_builder.get_keyboard("server")
)
else:
await query.edit_message_text(
"❌ Нельзя удалить local сервер",
reply_markup=menu_builder.get_keyboard("server")
)
elif callback == "srv_skip_password":
# Пропуск пароля при добавлении сервера
user_id = query.from_user.id
state = state_manager.get(user_id)
state.context["new_server"]["password"] = ""
state.input_type = "add_server_tags"
await query.edit_message_text(
"✅ Пароль пропущен (будет использоваться только ключ)\n\n"
"Введите *теги* через запятую (или нажмите Пропустить):\n"
"Пример: `web,prod`, `db,backup`\n\n"
"Теги помогают группировать серверы.",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton("⏭️ Пропустить", callback_data="srv_skip_tags")],
[InlineKeyboardButton("❌ Отмена", callback_data="server_menu")]
])
)
elif callback == "srv_skip_tags":
# Пропуск тегов при добавлении сервера
user_id = query.from_user.id
state = state_manager.get(user_id)
new_server = state.context.get("new_server", {})
if new_server.get("name") and new_server.get("host") and new_server.get("port") and new_server.get("user"):
if server_manager.add_server(
name=new_server["name"],
host=new_server["host"],
port=new_server["port"],
user=new_server["user"],
tags=[],
password=new_server.get("password", "")
):
await query.edit_message_text(
"✅ *Сервер добавлен*\n\n"
f"Имя: `{new_server['name']}`\n"
f"Host: `{new_server['host']}`\n"
f"Port: `{new_server['port']}`\n"
f"User: `{new_server['user']}`\n"
f"Tags: нет\n"
f"Password: {'установлен' if new_server.get('password') else 'не установлен'}\n\n"
f"Сервер сохранён в `.env` и доступен для выбора.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("server")
)
else:
await query.edit_message_text(
"❌ Ошибка: сервер с таким именем уже существует",
reply_markup=menu_builder.get_keyboard("server")
)
else:
await query.edit_message_text(
"❌ Ошибка: неполные данные сервера",
reply_markup=menu_builder.get_keyboard("server")
)
state.waiting_for_input = False
state.input_type = None
state.context.clear()
elif callback.startswith("server_select_"):
server_name = callback.replace("server_select_", "")
server = server_manager.get(server_name)
if server:
state.current_server = server_name
# Сброс рабочей директории при смене сервера
state.working_directory = None
await query.edit_message_text(
f"✅ *Сервер изменён*\n\n"
f"{server.display_name}\n"
f"📍 `{server.description}`\n\n"
f"Теперь команды выполняются на этом сервере.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id, state=state)
)
state.current_menu = "main"
else:
await query.edit_message_text(
f"❌ *Сервер не найден*\n\n"
f"Сервер `{server_name}` отсутствует в конфигурации.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("server")
)
elif callback == "settings_menu":
state.current_menu = "settings"
await query.edit_message_text(
"⚙️ *Настройки бота*",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("settings")
)
# Обработка команд выполнения
elif callback.startswith("cmd_"):
# Поиск команды в меню
command = None
for menu_items in menu_builder._menus.values():
for item in menu_items:
if item.callback == callback and item.command:
command = item.command
break
if command:
await execute_cli_command(query, command)
else:
await query.edit_message_text("❌ Команда не найдена")
# Настройки бота - только просмотр, изменение через .env
elif callback == "set_name":
await query.edit_message_text(
"📝 *Изменение имени бота*\n\n"
f"Текущее имя: `{config.name}`\n\n"
"Для изменения отредактируйте `.env`:\n"
"```\nBOT_NAME=Ваше имя\n```",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("settings")
)
elif callback == "set_description":
await query.edit_message_text(
"📄 *Изменение описания бота*\n\n"
f"Текущее описание: `{config.description}`\n\n"
"Для изменения отредактируйте `.env`:\n"
"```\nBOT_DESCRIPTION=Ваше описание\n```",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("settings")
)
elif callback == "set_icon":
await query.edit_message_text(
"🎨 *Изменение иконки бота*\n\n"
f"Текущая иконка: `{config.icon}`\n\n"
"Для изменения отредактируйте `.env`:\n"
"```\nBOT_ICON_EMOJI=🤖\n```",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("settings")
)
elif callback == "about":
await query.edit_message_text(
f" *О боте*\n\n"
f"*{config.icon} {config.name}*\n"
f"_{config.description}_\n\n"
f"*Версия:* `2.1.0`\n\n"
f"*Возможности:*\n"
f"• Выполнение CLI команд через Telegram\n"
f"• Поддержка локальных команд и SSH\n"
f"• Интерактивный ввод пароля (sudo)\n"
f"• Предустановленные команды\n"
f"• Управление серверами\n"
f"• Очистка ANSI-кодов и прогресс-баров\n"
f"• Форматирование длинного вывода\n"
f"• 💬 Чат с ИИ агентом (Qwen Code)\n\n"
f"*Рабочая директория:*\n"
f"`{config.working_directory}`\n\n"
f"Бот позволяет безопасно выполнять команды\n"
f"на вашем сервере через интерфейс Telegram.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id, state=state)
)
state.current_menu = "main"
elif callback in ["toggle_ai_chat", "toggle_ai_chat_on", "toggle_ai_chat_off"]:
# Переключаем режим чата с ИИ
state.ai_chat_mode = not state.ai_chat_mode
logger.info(f"toggle_ai_chat: user_id={user_id}, new_mode={state.ai_chat_mode}")
ai_status = "✅ ВКЛЮЧЕН" if state.ai_chat_mode else "❌ ВЫКЛЮЧЕН"
action = "включён" if state.ai_chat_mode else "выключен"
await query.edit_message_text(
f"🏠 *Главное меню*\n\n"
f"💬 *Чат с ИИ:* {ai_status}\n\n"
f"Режим чата с агентом {action}.\n"
f"Теперь все сообщения будут отправляться в Qwen Code.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id, state=state)
)
state.current_menu = "main"
# --- Обработчики меню памяти ---
elif callback == "memory_menu":
state.current_menu = "memory"
await query.edit_message_text(
"🧠 *Память ИИ*\n\n"
"Управление памятью чата с ИИ:\n"
"• Профиль — факты о вас, которые запомнил ИИ\n"
"• Статистика — количество сообщений и сессий\n"
"• Очистить — удалить историю переписки",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
elif callback == "memory_profile":
profile_summary = get_user_profile_summary(user_id)
if not profile_summary:
profile_summary = "📭 Профиль пуст\n\nФакты ещё не извлечены.\nНачните общаться с ИИ в чате."
await query.edit_message_text(
f"📋 *Ваш профиль*\n\n{profile_summary}",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
elif callback == "memory_stats":
stats = memory_manager.get_stats(user_id)
await query.edit_message_text(
f"📊 *Статистика памяти*\n\n"
f"• Сессий: `{stats['total_sessions']}`\n"
f"• Сообщений: `{stats['total_messages']}`\n"
f"• Фактов: `{stats['total_facts']}`",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
elif callback == "memory_clear":
# Показываем подтверждение
await query.edit_message_text(
"🗑️ *Очистка истории*\n\n"
"Вы уверены?\n"
"Это удалит всю историю сообщений.\n"
"Факты останутся (их можно удалить отдельно).",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton("🗑️ Да, очистить", callback_data="memory_clear_confirm")],
[InlineKeyboardButton("❌ Отмена", callback_data="memory_menu")]
])
)
elif callback == "memory_clear_confirm":
# Очищаем историю сообщений (в будущем можно добавить метод в memory_manager)
from memory_system import MemoryStorage
# Пока просто уведомляем
await query.edit_message_text(
"✅ *История очищена*\n\n"
"Функция полной очистки будет добавлена в следующей версии.\n"
"Пока очищается только история сессии в памяти бота.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
# Сбрасываем историю чата в состоянии
state.ai_chat_history = []
elif callback == "memory_compact":
# Вызываем команду /compact через send_message
await query.edit_message_text(
"🔄 **Запуск компактификации истории...**\n\n"
"_Сжатие старой истории в структурированный summary._\n"
"_Это может занять несколько секунд._",
parse_mode="Markdown"
)
# Получаем compactor и выполняем компактификацию
from bot.compaction import get_compactor
try:
compactor = get_compactor()
result = await compactor.compact()
if result.success:
if result.messages_compressed > 0:
await query.edit_message_text(
f"✅ **Компактификация завершена!**\n\n"
f"📊 Сжато сообщений: `{result.messages_compressed}`\n"
f"📝 Длина summary: `{result.summary_length}` символов\n"
f"💾 Экономия токенов: ~`{result.tokens_saved}`\n\n"
f"_Summary автоматически используется в контексте диалога._",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
else:
await query.edit_message_text(
" **Компактификация не требуется**\n\n"
"История пуста или уже компактная.",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
else:
await query.edit_message_text(
f"❌ **Ошибка компактификации:**\n\n{result.error}",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
except Exception as e:
await query.edit_message_text(
f"❌ **Ошибка:** {e}",
parse_mode="Markdown",
reply_markup=menu_builder.get_keyboard("memory")
)
elif callback == "opencode_model_menu":
state = state_manager.get(user_id)
current_model = state.opencode_model
state.current_ai_provider = "opencode"
keyboard = [
[InlineKeyboardButton(f"{'' if current_model == 'minimax' else ''} ⚡ minimax", callback_data="opencode_model_minimax")],
[InlineKeyboardButton(f"{'' if current_model == 'big_pickle' else ''} 🗃️ big-pickle", callback_data="opencode_model_big_pickle")],
[InlineKeyboardButton(f"{'' if current_model == 'gpt5' else ''} 🔬 gpt-5-nano", callback_data="opencode_model_gpt5")],
[InlineKeyboardButton("⬅️ Назад к AI-пресетам", callback_data="ai_presets")],
]
model_descriptions = {
"minimax": "Быстрая, бесплатная модель. Хорошо справляется с простыми задачами.",
"big_pickle": "Большая бесплатная модель. Лучше для сложных задач.",
"gpt5": "Самая мощная бесплатная модель. Требует больше времени."
}
await query.edit_message_text(
f"📡 **Выбор модели Opencode**\n\n"
f"Текущая модель: **{current_model}**\n\n"
f" Описание моделей:\n"
f"• ⚡ **minimax** — {model_descriptions['minimax']}\n"
f"• 🗃️ **big-pickle** — {model_descriptions['big_pickle']}\n"
f"• 🔬 **gpt-5-nano** — {model_descriptions['gpt5']}",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback.startswith("opencode_model_"):
model = callback.replace("opencode_model_", "")
state = state_manager.get(user_id)
state.opencode_model = model
# Обновляем модель в OpencodeProvider
from bot.providers.opencode_provider import OpencodeProvider
provider = OpencodeProvider()
provider.set_model(user_id, model)
model_names = {
"minimax": "⚡ minimax",
"big_pickle": "🗃️ big-pickle",
"gpt5": "🔬 gpt-5-nano"
}
await query.answer(f"✅ Модель изменена на {model_names.get(model, model)}")
# Показываем меню снова
keyboard = [
[InlineKeyboardButton(f"{'' if model == 'minimax' else ''} ⚡ minimax", callback_data="opencode_model_minimax")],
[InlineKeyboardButton(f"{'' if model == 'big_pickle' else ''} 🗃️ big-pickle", callback_data="opencode_model_big_pickle")],
[InlineKeyboardButton(f"{'' if model == 'gpt5' else ''} 🔬 gpt-5-nano", callback_data="opencode_model_gpt5")],
[InlineKeyboardButton("⬅️ Назад к AI-пресетам", callback_data="ai_presets")],
]
await query.edit_message_text(
f"📡 **Выбор модели Opencode**\n\n"
f"Текущая модель: **{model_names.get(model, model)}**\n\n"
f"✅ *Модель изменена!*\n\n"
f"Выберите модель или вернитесь назад:",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
# --- Обработчики меню выбора AI-провайдера ---
elif callback == "ai_provider_selection_menu":
state = state_manager.get(user_id)
current_provider = state.current_ai_provider
keyboard = [
[InlineKeyboardButton(f"{'' if current_provider == 'qwen' else ''} 🔄 Qwen Code", callback_data="ai_provider_qwen")],
[InlineKeyboardButton(f"{'' if current_provider == 'opencode' else ''} 📡 Opencode ▶", callback_data="opencode_model_menu")],
[InlineKeyboardButton(f"{'' if current_provider == 'gigachat' else ''} 🧠 GigaChat ▶", callback_data="gigachat_model_menu")],
[InlineKeyboardButton("⬅️ Назад", callback_data="settings")],
]
provider_descriptions = {
"qwen": "Бесплатный локальный AI от Alibaba",
"opencode": "Бесплатные модели (minimax, big-pickle, gpt-5-nano)",
"gigachat": "Российский AI от Сбера (Lite, Pro, Max)"
}
await query.edit_message_text(
f"🤖 **Выбор AI-провайдера**\n\n"
f"Текущий провайдер: **{current_provider.upper()}**\n\n"
f" Описание провайдеров:\n"
f"• 🔄 **Qwen Code** — {provider_descriptions['qwen']}\n"
f"• 📡 **Opencode** — {provider_descriptions['opencode']}\n"
f"• 🧠 **GigaChat** — {provider_descriptions['gigachat']}",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback == "ai_provider_qwen":
state = state_manager.get(user_id)
state.current_ai_provider = "qwen"
await query.answer("✅ Переключено на Qwen Code")
current_provider = "qwen"
keyboard = [
[InlineKeyboardButton(f"{'' if current_provider == 'qwen' else ''} 🔄 Qwen Code", callback_data="ai_provider_qwen")],
[InlineKeyboardButton(f"{'' if current_provider == 'opencode' else ''} 📡 Opencode ▶", callback_data="opencode_model_menu")],
[InlineKeyboardButton(f"{'' if current_provider == 'gigachat' else ''} 🧠 GigaChat ▶", callback_data="gigachat_model_menu")],
[InlineKeyboardButton("⬅️ Назад", callback_data="settings")],
]
await query.edit_message_text(
f"🤖 **Выбор AI-провайдера**\n\n"
f"Текущий провайдер: **QWEN CODE**\n\n"
f"✅ *Провайдер изменён!*\n\n"
f" Описание провайдеров:\n"
f"• 🔄 **Qwen Code** — Бесплатный локальный AI от Alibaba\n"
f"• 📡 **Opencode** — Бесплатные модели (minimax, big-pickle, gpt-5-nano)\n"
f"• 🧠 **GigaChat** — Российский AI от Сбера (Lite, Pro, Max)",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback == "gigachat_model_menu":
state = state_manager.get(user_id)
current_model = state.gigachat_model
state.current_ai_provider = "gigachat"
keyboard = [
[InlineKeyboardButton(f"{'' if current_model == 'lite' else ''} 📱 GigaChat Lite", callback_data="gigachat_model_lite")],
[InlineKeyboardButton(f"{'' if current_model == 'pro' else ''} 🚀 GigaChat Pro", callback_data="gigachat_model_pro")],
[InlineKeyboardButton(f"{'' if current_model == 'max' else ''} 💎 GigaChat Max", callback_data="gigachat_model_max")],
[InlineKeyboardButton("⬅️ Назад", callback_data="ai_provider_selection_menu")],
]
model_descriptions = {
"lite": "Быстрая и экономичная модель для простых задач",
"pro": "Баланс скорости и качества для большинства задач",
"max": "Самая мощная модель для сложных задач"
}
await query.edit_message_text(
f"🧠 **Выбор модели GigaChat**\n\n"
f"Текущая модель: **{current_model.upper()}**\n\n"
f" Описание моделей:\n"
f"• 📱 **Lite** — {model_descriptions['lite']}\n"
f"• 🚀 **Pro** — {model_descriptions['pro']}\n"
f"• 💎 **Max** — {model_descriptions['max']}",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)
elif callback.startswith("gigachat_model_"):
model = callback.replace("gigachat_model_", "")
state = state_manager.get(user_id)
state.gigachat_model = model
state.current_ai_provider = "gigachat"
model_names = {
"lite": "📱 GigaChat Lite",
"pro": "🚀 GigaChat Pro",
"max": "💎 GigaChat Max"
}
await query.answer(f"✅ Модель изменена на {model_names.get(model, model)}")
current_model = model
keyboard = [
[InlineKeyboardButton(f"{'' if current_model == 'lite' else ''} 📱 GigaChat Lite", callback_data="gigachat_model_lite")],
[InlineKeyboardButton(f"{'' if current_model == 'pro' else ''} 🚀 GigaChat Pro", callback_data="gigachat_model_pro")],
[InlineKeyboardButton(f"{'' if current_model == 'max' else ''} 💎 GigaChat Max", callback_data="gigachat_model_max")],
[InlineKeyboardButton("⬅️ Назад", callback_data="ai_provider_selection_menu")],
]
await query.edit_message_text(
f"🧠 **Выбор модели GigaChat**\n\n"
f"Текущая модель: **{model_names.get(model, model)}**\n\n"
f"✅ *Модель изменена!*\n\n"
f"Теперь используется GigaChat с выбранной моделью.",
parse_mode="Markdown",
reply_markup=InlineKeyboardMarkup(keyboard)
)