672 lines
31 KiB
Python
672 lines
31 KiB
Python
#!/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
|
||
|
||
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
|
||
|
||
elif callback.startswith("continue_output_"):
|
||
# Пользователь нажал "Продолжить"
|
||
remaining = int(callback.replace("continue_output_", ""))
|
||
state = state_manager.get(user_id)
|
||
logger.info(f"callback continue_output_{remaining}: user_id={user_id}")
|
||
state.waiting_for_output_control = False
|
||
state.continue_output = True
|
||
# Удаляем сообщение с кнопками
|
||
try:
|
||
await query.delete_message()
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось удалить сообщение с кнопками: {e}")
|
||
# Устанавливаем event чтобы разблокировать send_long_message
|
||
if state.output_continue_event:
|
||
logger.info("callback: устанавливаем continue_event")
|
||
state.output_continue_event.set()
|
||
else:
|
||
logger.warning("callback: output_continue_event не найден!")
|
||
await query.answer()
|
||
return
|
||
|
||
elif callback == "cancel_output":
|
||
# Пользователь нажал "Отменить"
|
||
logger.info(f"callback cancel_output: user_id={user_id}")
|
||
state = state_manager.get(user_id)
|
||
state.waiting_for_output_control = False
|
||
state.continue_output = False
|
||
# Удаляем сообщение с кнопками
|
||
try:
|
||
await query.delete_message()
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось удалить сообщение с кнопками: {e}")
|
||
# Устанавливаем event чтобы разблокировать send_long_message
|
||
if state.output_continue_event:
|
||
logger.info("callback: устанавливаем continue_event (отмена)")
|
||
state.output_continue_event.set()
|
||
else:
|
||
logger.warning("callback: output_continue_event не найден!")
|
||
await query.answer()
|
||
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 == "server_menu":
|
||
# Динамическое обновление меню серверов с кнопками управления
|
||
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 == "access_menu":
|
||
await query.edit_message_text(
|
||
"👥 *Управление доступом*",
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("access")
|
||
)
|
||
|
||
# Обработка команд выполнения
|
||
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 == "show_access":
|
||
if config.allowed_users:
|
||
text = "👥 *Разрешённые пользователи:*\n" + "\n".join(f"• `{uid}`" for uid in config.allowed_users)
|
||
else:
|
||
text = "👥 *Доступ открыт для всех*\n\n(список разрешённых пользователей пуст)"
|
||
await query.edit_message_text(text, parse_mode="Markdown")
|
||
|
||
elif callback == "add_access":
|
||
await query.edit_message_text(
|
||
"➕ *Добавление пользователя*\n\n"
|
||
"Для добавления пользователя отредактируйте `.env`:\n"
|
||
"```\nALLOWED_USERS=123456789,987654321\n```\n"
|
||
"Ваш ID можно узнать через @userinfobot",
|
||
parse_mode="Markdown"
|
||
)
|
||
|
||
elif callback == "remove_access":
|
||
if config.allowed_users:
|
||
text = "➖ *Удаление пользователя*\n\n" + "\n".join(f"• `{uid}`" for uid in config.allowed_users)
|
||
text += "\n\nУдалите ID из `.env` чтобы убрать доступ"
|
||
else:
|
||
text = "➖ Список пуст, некого удалять"
|
||
await query.edit_message_text(text, parse_mode="Markdown")
|
||
|
||
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"
|
||
"_Недостаточно сообщений для сжатия или summary уже актуален._",
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("memory")
|
||
)
|
||
else:
|
||
await query.edit_message_text(
|
||
f"⚠️ **Ошибка компактификации:**\n`{result.error}`",
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("memory")
|
||
)
|
||
except Exception as e:
|
||
logger.exception(f"Ошибка в memory_compact: {e}")
|
||
await query.edit_message_text(
|
||
f"⚠️ **Ошибка компактификации:**\n`{e}`",
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("memory")
|
||
)
|
||
|
||
# --- Обработчики меню AI-провайдера ---
|
||
elif callback == "ai_provider_menu":
|
||
state.current_menu = "ai_provider"
|
||
|
||
# Получаем текущего провайдера
|
||
from bot.ai_provider_manager import get_ai_provider_manager
|
||
provider_manager = get_ai_provider_manager()
|
||
current_provider = provider_manager.get_current_provider(state)
|
||
providers_info = provider_manager.get_all_providers_info(current_provider)
|
||
|
||
output = "🤖 **AI-провайдеры**\n\n"
|
||
output += f"*Текущий провайдер:* "
|
||
|
||
for info in providers_info:
|
||
icon = "✅" if info.is_active else "⬜"
|
||
status = "✓ Доступен" if info.available else "✗ Недоступен"
|
||
output += f"\n\n{icon} **{info.name}** — {status}\n"
|
||
output += f"_{info.description}_\n"
|
||
|
||
output += "\n\nВыберите действие:"
|
||
|
||
await query.edit_message_text(
|
||
output,
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("ai_provider")
|
||
)
|
||
|
||
elif callback == "ai_provider_toggle":
|
||
# Переключаем провайдер
|
||
from bot.ai_provider_manager import get_ai_provider_manager
|
||
provider_manager = get_ai_provider_manager()
|
||
|
||
current_provider = provider_manager.get_current_provider(state)
|
||
new_provider = "gigachat" if current_provider == "qwen" else "qwen"
|
||
|
||
success, message = provider_manager.switch_provider(user_id, new_provider, state_manager)
|
||
|
||
if success:
|
||
provider_info = provider_manager.get_provider_info(new_provider, is_active=True)
|
||
await query.edit_message_text(
|
||
f"{message}\n\n"
|
||
f"**{provider_info.name}**\n"
|
||
f"_{provider_info.description}_",
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("ai_provider")
|
||
)
|
||
else:
|
||
await query.edit_message_text(
|
||
f"❌ {message}\n\n"
|
||
"Проверьте настройки в .env файле.",
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("ai_provider")
|
||
)
|
||
|
||
elif callback == "ai_provider_info":
|
||
# Показываем подробную информацию
|
||
from bot.ai_provider_manager import get_ai_provider_manager
|
||
provider_manager = get_ai_provider_manager()
|
||
current_provider = provider_manager.get_current_provider(state)
|
||
|
||
output = "ℹ️ **Информация о провайдерах**\n\n"
|
||
|
||
# Qwen
|
||
output += "**🔹 Qwen Code**\n"
|
||
output += "Alibaba Qwen Code CLI — мощный AI-ассистент с:\n"
|
||
output += "• Поддержкой инструментов (поиск, RSS, SSH, cron)\n"
|
||
output += "• Потоковым выводом ответа\n"
|
||
output += "• Контекстом до 256K токенов\n"
|
||
output += "• RAG-памятью на ChromaDB\n\n"
|
||
|
||
# GigaChat
|
||
output += "**🟢 GigaChat**\n"
|
||
output += "Sber GigaChat API — российская AI-модель:\n"
|
||
output += "• Поддержка русского языка из коробки\n"
|
||
output += "• Модели: GigaChat-Pro, GigaChat-Max\n"
|
||
output += "• Генерация ответов и изображений\n"
|
||
output += "• Требует настройки в .env\n\n"
|
||
|
||
output += f"*Текущий провайдер:* `{current_provider}`\n"
|
||
output += "\nИспользуйте `/ai` для переключения."
|
||
|
||
await query.edit_message_text(
|
||
output,
|
||
parse_mode="Markdown",
|
||
reply_markup=menu_builder.get_keyboard("ai_provider")
|
||
)
|
||
|