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

299 lines
12 KiB
Python
Raw 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
"""
Обработчики команд для переключения AI-пресетов.
Доступные пресеты:
- off: ИИ отключен, режим CLI команд
- qwen: Qwen Code (бесплатно, локально)
- giga_auto: GigaChat авто-переключение (Lite/Pro)
- giga_lite: GigaChat Lite (дешевле)
- giga_pro: GigaChat Pro (максимальное качество)
"""
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import CommandHandler, CallbackQueryHandler
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 bot.config import state_manager
logger = logging.getLogger(__name__)
# Описание пресетов
PRESET_DESCRIPTIONS = {
AI_PRESET_OFF: {
"name": "❌ ИИ Отключен",
"description": "Режим CLI команд. Бот выполняет команды напрямую.",
"icon": "⌨️"
},
AI_PRESET_QWEN: {
"name": "🤖 Qwen Code",
"description": "Бесплатно, локально. Лучший для кода и работы с файлами.",
"icon": "💻"
},
AI_PRESET_GIGA_AUTO: {
"name": "🔄 GigaChat Авто",
"description": "Умное переключение Lite/Pro. Простые → Lite, сложные → Pro.",
"icon": "🧠"
},
AI_PRESET_GIGA_LITE: {
"name": "⚡ GigaChat Lite",
"description": "Быстро и дёшево. Для простых вопросов и чата.",
"icon": "🚀"
},
AI_PRESET_GIGA_PRO: {
"name": "🔥 GigaChat Pro",
"description": "Максимальное качество. Для сложных творческих задач.",
"icon": "👑"
},
AI_PRESET_GIGA_MAX: {
"name": "💎 GigaChat Max",
"description": "Топовая модель для самых сложных задач.",
"icon": "💎"
},
AI_PRESET_OPENCODE: {
"name": "⚡ Opencode",
"description": "Бесплатные модели (minimax, big-pickle, gpt-5-nano).",
"icon": "🚀"
},
}
def get_preset_display_name(preset: str) -> str:
"""Получить отображаемое имя пресета."""
desc = PRESET_DESCRIPTIONS.get(preset, {})
return f"{desc.get('icon', '')} {desc.get('name', preset)}"
async def ai_presets_command(update: Update, context):
"""Показать меню выбора AI-пресета."""
user_id = update.effective_user.id
state = state_manager.get(user_id)
current_preset = state.ai_preset
# Формируем меню - Opencode и GigaChat теперь открывают подменю выбора моделей
keyboard = [
[
InlineKeyboardButton(
f"{'' if current_preset == AI_PRESET_OFF else ''} {PRESET_DESCRIPTIONS[AI_PRESET_OFF]['icon']} ИИ Отключен",
callback_data=f"ai_preset_{AI_PRESET_OFF}"
)
],
[
InlineKeyboardButton(
f"{'' if current_preset == AI_PRESET_QWEN else ''} {PRESET_DESCRIPTIONS[AI_PRESET_QWEN]['icon']} Qwen Code",
callback_data=f"ai_preset_{AI_PRESET_QWEN}"
)
],
[
InlineKeyboardButton(
f"{'' if current_preset == AI_PRESET_GIGA_AUTO else ''} {PRESET_DESCRIPTIONS[AI_PRESET_GIGA_AUTO]['icon']} GigaChat Авто",
callback_data=f"ai_preset_{AI_PRESET_GIGA_AUTO}"
)
],
# Кнопка GigaChat с подменю - убираем Lite и Pro из основного меню
[
InlineKeyboardButton(
f"{'' if current_preset in [AI_PRESET_GIGA_LITE, AI_PRESET_GIGA_PRO, AI_PRESET_GIGA_MAX] else ''} {PRESET_DESCRIPTIONS[AI_PRESET_GIGA_LITE]['icon']} GigaChat ▶",
callback_data="gigachat_submenu"
)
],
# Кнопка Opencode с подменю
[
InlineKeyboardButton(
f"{'' if current_preset == AI_PRESET_OPENCODE else ''} {PRESET_DESCRIPTIONS[AI_PRESET_OPENCODE]['icon']} Opencode ▶",
callback_data="opencode_submenu"
)
],
]
reply_markup = InlineKeyboardMarkup(keyboard)
current_name = get_preset_display_name(current_preset)
output = f"🎛️ **Панель управления AI**\n\n"
output += f"**Текущий пресет:** {current_name}\n\n"
output += "Выберите AI-провайдер:\n\n"
output += " **Описание пресетов:**\n"
output += f"{PRESET_DESCRIPTIONS[AI_PRESET_OFF]['icon']} **ИИ Отключен** — {PRESET_DESCRIPTIONS[AI_PRESET_OFF]['description']}\n"
output += f"{PRESET_DESCRIPTIONS[AI_PRESET_QWEN]['icon']} **Qwen Code** — {PRESET_DESCRIPTIONS[AI_PRESET_QWEN]['description']}\n"
output += f"{PRESET_DESCRIPTIONS[AI_PRESET_GIGA_AUTO]['icon']} **GigaChat Авто** — {PRESET_DESCRIPTIONS[AI_PRESET_GIGA_AUTO]['description']}\n"
output += f"{PRESET_DESCRIPTIONS[AI_PRESET_GIGA_LITE]['icon']} **GigaChat** — {PRESET_DESCRIPTIONS[AI_PRESET_GIGA_LITE]['description']}\n"
output += f"{PRESET_DESCRIPTIONS[AI_PRESET_OPENCODE]['icon']} **Opencode** — {PRESET_DESCRIPTIONS[AI_PRESET_OPENCODE]['description']}"
await update.message.reply_text(output, parse_mode="Markdown", reply_markup=reply_markup)
async def ai_preset_callback(update: Update, context):
"""Обработка выбора пресета из инлайн-меню."""
user_id = update.effective_user.id
query = update.callback_query
await query.answer()
# Извлекаем название пресета из callback_data
preset = query.data.replace("ai_preset_", "")
if preset not in PRESET_DESCRIPTIONS:
await query.edit_message_text("❌ Неверный пресет")
return
state = state_manager.get(user_id)
old_preset = state.ai_preset
state.ai_preset = preset
# Обновляем ai_chat_mode и current_ai_provider для совместимости
if preset == AI_PRESET_OFF:
state.ai_chat_mode = False
state.current_ai_provider = "none"
else:
state.ai_chat_mode = True
# Для совместимости с существующим кодом
if preset == AI_PRESET_QWEN:
state.current_ai_provider = "qwen"
elif preset == AI_PRESET_OPENCODE:
state.current_ai_provider = "opencode"
else: # Любой GigaChat
state.current_ai_provider = "gigachat"
preset_name = get_preset_display_name(preset)
output = f"✅ **Переключено на:** {preset_name}\n\n"
output += f"{PRESET_DESCRIPTIONS[preset]['description']}"
# Обновляем инлайн-меню - с подменю для Opencode и GigaChat
keyboard = [
[
InlineKeyboardButton(
f"{'' if preset == AI_PRESET_OFF else ''} {PRESET_DESCRIPTIONS[AI_PRESET_OFF]['icon']} ИИ Отключен",
callback_data=f"ai_preset_{AI_PRESET_OFF}"
)
],
[
InlineKeyboardButton(
f"{'' if preset == AI_PRESET_QWEN else ''} {PRESET_DESCRIPTIONS[AI_PRESET_QWEN]['icon']} Qwen Code",
callback_data=f"ai_preset_{AI_PRESET_QWEN}"
)
],
[
InlineKeyboardButton(
f"{'' if preset == AI_PRESET_GIGA_AUTO else ''} {PRESET_DESCRIPTIONS[AI_PRESET_GIGA_AUTO]['icon']} GigaChat Авто",
callback_data=f"ai_preset_{AI_PRESET_GIGA_AUTO}"
)
],
[
InlineKeyboardButton(
f"{'' if preset in [AI_PRESET_GIGA_LITE, AI_PRESET_GIGA_PRO, AI_PRESET_GIGA_MAX] else ''} {PRESET_DESCRIPTIONS[AI_PRESET_GIGA_LITE]['icon']} GigaChat ▶",
callback_data="gigachat_submenu"
)
],
[
InlineKeyboardButton(
f"{'' if preset == AI_PRESET_OPENCODE else ''} {PRESET_DESCRIPTIONS[AI_PRESET_OPENCODE]['icon']} Opencode ▶",
callback_data="opencode_submenu"
)
],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(output, parse_mode="Markdown", reply_markup=reply_markup)
logger.info(f"Пользователь {user_id} переключил AI-пресет: {old_preset}{preset}")
# Быстрые команды для переключения одним сообщением
async def ai_off_command(update: Update, context):
"""Быстрое переключение на ИИ отключен."""
await switch_preset(update, AI_PRESET_OFF)
async def ai_qwen_command(update: Update, context):
"""Быстрое переключение на Qwen Code."""
await switch_preset(update, AI_PRESET_QWEN)
async def ai_giga_auto_command(update: Update, context):
"""Быстрое переключение на GigaChat Авто."""
await switch_preset(update, AI_PRESET_GIGA_AUTO)
async def ai_giga_lite_command(update: Update, context):
"""Быстрое переключение на GigaChat Lite."""
await switch_preset(update, AI_PRESET_GIGA_LITE)
async def ai_giga_pro_command(update: Update, context):
"""Быстрое переключение на GigaChat Pro."""
await switch_preset(update, AI_PRESET_GIGA_PRO)
async def ai_giga_max_command(update: Update, context):
"""Быстрое переключение на GigaChat Max."""
await switch_preset(update, AI_PRESET_GIGA_MAX)
async def ai_opencode_command(update: Update, context):
"""Быстрое переключение на Opencode."""
await switch_preset(update, AI_PRESET_OPENCODE)
async def switch_preset(update: Update, preset: str):
"""Переключить пресет и показать уведомление."""
user_id = update.effective_user.id
state = state_manager.get(user_id)
old_preset = state.ai_preset
state.ai_preset = preset
# Обновляем совместимость
if preset == AI_PRESET_OFF:
state.ai_chat_mode = False
state.current_ai_provider = "none"
else:
state.ai_chat_mode = True
if preset == AI_PRESET_QWEN:
state.current_ai_provider = "qwen"
elif preset == AI_PRESET_OPENCODE:
state.current_ai_provider = "opencode"
else:
state.current_ai_provider = "gigachat"
preset_name = get_preset_display_name(preset)
output = f"✅ **AI-пресет переключен**\n\n"
output += f"**Текущий:** {preset_name}\n"
output += f"_{PRESET_DESCRIPTIONS[preset]['description']}_\n\n"
if old_preset != preset:
output += f"~{get_preset_display_name(old_preset)}~ → ✅ {preset_name}"
await update.message.reply_text(output, parse_mode="Markdown")
logger.info(f"Пользователь {user_id} переключил AI-пресет: {old_preset}{preset}")
def register_ai_preset_handlers(dispatcher):
"""Зарегистрировать обработчики AI-пресетов."""
# Основное меню
dispatcher.add_handler(CommandHandler("ai_presets", ai_presets_command))
# Callback для инлайн-меню
dispatcher.add_handler(CallbackQueryHandler(ai_preset_callback, pattern="^ai_preset_"))
# Быстрые команды
dispatcher.add_handler(CommandHandler("ai_off", ai_off_command))
dispatcher.add_handler(CommandHandler("ai_qwen", ai_qwen_command))
dispatcher.add_handler(CommandHandler("ai_giga_auto", ai_giga_auto_command))
dispatcher.add_handler(CommandHandler("ai_giga_lite", ai_giga_lite_command))
dispatcher.add_handler(CommandHandler("ai_giga_pro", ai_giga_pro_command))
dispatcher.add_handler(CommandHandler("ai_giga_max", ai_giga_max_command))
dispatcher.add_handler(CommandHandler("ai_opencode", ai_opencode_command))
logger.info("Обработчики AI-пресетов зарегистрированы")