diff --git a/bot.py b/bot.py index dc387f1..6af3d6b 100644 --- a/bot.py +++ b/bot.py @@ -298,6 +298,7 @@ class UserState: working_directory: Optional[str] = None current_server: str = "local" # Имя текущего сервера editing_server: Optional[str] = None # Имя сервера, который редактируем + ai_chat_mode: bool = False # Режим чата с ИИ агентом class StateManager: @@ -771,17 +772,33 @@ class MenuBuilder: def get_menu(self, menu_name: str) -> List[MenuItem]: return self._menus.get(menu_name, []) - def get_keyboard(self, menu_name: str) -> InlineKeyboardMarkup: + def get_keyboard(self, menu_name: str, user_id: int = None) -> InlineKeyboardMarkup: """Создает InlineKeyboard для меню.""" items = self._menus.get(menu_name, []) keyboard = [] - for item in items: - # Иконка уже есть в label, поэтому не добавляем её отдельно - button = InlineKeyboardButton( - item.label, - callback_data=item.callback - ) - keyboard.append([button]) + + # Для главного меню — динамически меняем кнопку ИИ + if menu_name == "main" and user_id: + from bot import state_manager + state = state_manager.get(user_id) + ai_status = "✅" if state.ai_chat_mode else "❌" + + for item in items: + if item.callback == "toggle_ai_chat": + # Меняем текст кнопки + label = f"{ai_status} Чат с ИИ агентом" + button = InlineKeyboardButton(label, callback_data=item.callback) + else: + button = InlineKeyboardButton(item.label, callback_data=item.callback) + keyboard.append([button]) + else: + for item in items: + button = InlineKeyboardButton( + item.label, + callback_data=item.callback + ) + keyboard.append([button]) + return InlineKeyboardMarkup(keyboard) @@ -846,6 +863,7 @@ def init_menus(): main_menu = [ MenuItem("🖥️ Выбор сервера", "server_menu", icon="🖥️"), MenuItem("📋 Предустановленные команды", "preset_menu", icon="📋"), + MenuItem("💬 Чат с ИИ агентом", "toggle_ai_chat", icon="💬"), MenuItem("⚙️ Настройки бота", "settings_menu", icon="⚙️"), MenuItem("ℹ️ О боте", "about", icon="ℹ️"), ] @@ -955,7 +973,7 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE): f"Или выберите сервер в меню.\n" f"Команда /help покажет справку.", parse_mode="Markdown", - reply_markup=menu_builder.get_keyboard("main") + reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id if query else update.effective_user.id) ) @@ -980,7 +998,7 @@ async def menu_command(update: Update, context: ContextTypes.DEFAULT_TYPE): f"📁 *Директория:* `{working_dir}`\n\n" f"Выберите действие:", parse_mode="Markdown", - reply_markup=menu_builder.get_keyboard("main") + reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id if query else update.effective_user.id) ) @@ -1050,10 +1068,14 @@ async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): # Обработка навигации 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") + reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id if query else update.effective_user.id) ) elif callback == "preset_menu": @@ -1299,7 +1321,7 @@ async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): f"📍 `{server.description}`\n\n" f"Теперь команды выполняются на этом сервере.", parse_mode="Markdown", - reply_markup=menu_builder.get_keyboard("main") + reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id if query else update.effective_user.id) ) state.current_menu = "main" else: @@ -1408,13 +1430,31 @@ async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): f"• Предустановленные команды\n" f"• Управление серверами\n" f"• Очистка ANSI-кодов и прогресс-баров\n" - f"• Форматирование длинного вывода\n\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") + reply_markup=menu_builder.get_keyboard("main", user_id=query.from_user.id if query else update.effective_user.id) + ) + state.current_menu = "main" + + elif callback == "toggle_ai_chat": + # Переключаем режим + state.ai_chat_mode = not 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 if query else update.effective_user.id) ) state.current_menu = "main" @@ -1718,19 +1758,59 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE if ssh_session and ssh_session.waiting_for_input: await handle_ssh_session_input(update, text, ssh_session) return - + # Проверка: не активная ли локальная сессия ожидает ввода local_session = local_session_manager.get_session(user_id) if local_session and local_session.waiting_for_input: await handle_local_session_input(update, text, local_session) return + # ПРОВЕРКА: режим чата с ИИ агентом + if state.ai_chat_mode: + logger.info(f"Пользователь {user_id} отправил задачу ИИ: {text}") + await handle_ai_task(update, text) + return + # Любое текстовое сообщение = CLI команда logger.info(f"Пользователь {user_id} отправил команду: {text}") await execute_cli_command_from_message(update, text) +async def handle_ai_task(update: Update, text: str): + """Обработка задачи для ИИ агента.""" + user_id = update.effective_user.id + + # Отправляем статус + status_msg = await update.message.reply_text("⏳ 🤖 Думаю...", parse_mode="Markdown") + + output_buffer = [] + + def on_output(text: str): + output_buffer.append(text) + + def on_oauth_url(url: str): + pass # OAuth обрабатывается автоматически + + # Выполняем задачу + result = await qwen_manager.run_task(user_id, text, on_output, on_oauth_url) + + # Показываем результат + full_output = "".join(output_buffer).strip() + + if not full_output: + full_output = result + + if len(full_output) > 4000: + full_output = full_output[:4000] + "\n... (вывод обрезан)" + + await status_msg.edit_text( + f"🤖 *Результат:*\n\n" + f"```\n{full_output}\n```", + parse_mode="Markdown" + ) + + async def handle_ssh_session_input(update: Update, text: str, session: SSHSession): """Обработка ввода пользователя в активную SSH-сессию.""" user_id = update.effective_user.id