feat: режим чата с ИИ агентом через меню

Новые возможности:
- Кнопка '💬 Чат с ИИ агентом' в главном меню
- Включение/выключение режима чата
- Все сообщения отправляются в Qwen Code когда режим включён
- Индикация статуса в кнопке (/)
- Индикация статуса в главном меню

Изменения:
- UserState.ai_chat_mode — флаг режима
- handle_ai_task() — обработка задач для ИИ
- Динамическое обновление кнопки в get_keyboard()
- Обновление 'О боте' с информацией о чате с ИИ

Использование:
1. Меню → 💬 Чат с ИИ агентом (включить)
2. Отправлять сообщения как задачи для ИИ
3. Меню → 💬 Чат с ИИ агентом (выключить)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
mirivlad 2026-02-24 04:22:45 +08:00
parent cac597688d
commit b1ccda8a13
1 changed files with 96 additions and 16 deletions

110
bot.py
View File

@ -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"
@ -1725,12 +1765,52 @@ async def handle_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE
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