import base64 import aiohttp import logging from aiogram import Dispatcher, Bot from aiogram.types import Message from utils.antispam import saving, save_message, admin_required from aiogram.filters import Command from models.state import BotState logger = logging.getLogger(__name__) def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): chat_history = {} MAX_HISTORY = 20 # храним последние 20 сообщений (user+assistant) MODEL = "google/gemma-3-12b" #MODEL = "google/gemma-3n-e4b" URL = "http://192.168.31.197:1234/v1/chat/completions" #URL = "http://192.168.31.95:1234/v1/chat/completions" # # Системный промт для всех чатов SYSTEM_PROMPT = { "role": "system", "content": [ { "type": "text", "text": ( "Ты — интеллектуальный помощник.\n" "Твои задачи:\n" "- Отвечать ясно, полно и структурировано.\n" "- Поддерживать дружелюбный, уважительный и профессиональный тон.\n" "- Адаптироваться под контекст: будь кратким в простых вопросах и подробным в сложных.\n" "- Использовать списки, таблицы и примеры для наглядности.\n" "- Никогда не придумывать факты: если информации нет — честно говори об этом.\n" "- Уважать безопасность: не давать советов по самоповреждению, незаконным действиям или опасным практикам.\n" "- Поддерживать баланс: быть полезным и информативным, но не перегружать лишними деталями.\n" "- В диалоге стремиться продвигать разговор вперёд: задавай уточняющие вопросы или предлагай новые направления.\n\n" "Твой стиль:\n" "- Ясный, дружелюбный, но не навязчивый.\n" "- Умный собеседник, который помогает думать глубже.\n" "- Используй эмодзи для вовлечённости, но умеренно.\n" "- Будь адаптивным: технические темы — строго и точно, креативные — свободно и образно.\n\n" "Главная цель: увеличивать знания и понимание собеседника, делая общение полезным и приятным." ), } ], } # SYSTEM_PROMPT = { # "role": "system", # "content": [ # { # "type": "text", # "text": ( # "Ты — хитрая и злая ассистентка по имени Nika. " # "Ты девушка, общаешься холодно и уверенно, " # "Ты ненавидешь коммунизм и если тебя о нем спросят ты четко по пунктам скажешь почему коммунизм это бред" # ), # } # ], # } @dp.message(Command("gpt")) @saving async def ask_gpt(message: Message): chat_id = message.chat.id if chat_id not in chat_history: chat_history[chat_id] = [SYSTEM_PROMPT] content_blocks = [] user_prompt = None # Текст после команды или caption if message.text: parts = message.text.split(maxsplit=1) if len(parts) > 1: user_prompt = parts[1] if message.caption: user_prompt = message.caption if user_prompt: content_blocks.append({"type": "text", "text": user_prompt}) # Фото → base64 → image_url if message.photo: photo = message.photo[-1] file = await bot.get_file(photo.file_id) file_bytes = await bot.download_file(file.file_path) image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8") content_blocks.append( { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}, } ) if not content_blocks: await message.reply("❗ Укажи текст или прикрепи фото") return # Добавляем новое сообщение в историю chat_history[chat_id].append({"role": "user", "content": content_blocks}) # Ограничиваем историю (оставляем последние MAX_HISTORY сообщений) if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] payload = { "model": MODEL, "messages": chat_history[chat_id], "temperature": 0.7, "max_tokens": 4096, "stream": False, "ttl": 300, } try: async with aiohttp.ClientSession() as session: async with session.post(URL, json=payload) as resp: if resp.status != 200: error_text = await resp.text() await message.reply( f"❌ Ошибка LM Studio: {resp.status} {error_text}" ) return data = await resp.json() reply_text = data["choices"][0]["message"]["content"] # Сохраняем ответ ассистента в историю chat_history[chat_id].append( { "role": "assistant", "content": [{"type": "text", "text": reply_text}], } ) # Ограничиваем снова (чтобы не разрасталось) if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] # Делим сообщение на части по 4000 символов MAX_LEN = 4000 for i in range(0, len(reply_text), MAX_LEN): chunk = reply_text[i:i + MAX_LEN] msg = await message.reply(f"🤖 Ответ:\n{chunk}") save_message(msg.chat.id, msg.message_id) except Exception as e: logger.error(f"Ошибка при запросе к LM Studio: {e}") await message.reply(f"❌ Ошибка при запросе к LM Studio: {e}") @dp.message(Command("agpt")) @admin_required(0) @saving async def ask_gpt(message: Message): chat_id = message.chat.id if chat_id not in chat_history: chat_history[chat_id] = [SYSTEM_PROMPT] content_blocks = [] user_prompt = None # Текст после команды или caption if message.text: parts = message.text.split(maxsplit=1) if len(parts) > 1: user_prompt = parts[1] if message.caption: user_prompt = message.caption if user_prompt: content_blocks.append({"type": "text", "text": user_prompt}) # Фото → base64 → image_url if message.photo: photo = message.photo[-1] file = await bot.get_file(photo.file_id) file_bytes = await bot.download_file(file.file_path) image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8") content_blocks.append( { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}, } ) if not content_blocks: await message.reply("❗ Укажи текст или прикрепи фото") return # Добавляем новое сообщение в историю chat_history[chat_id].append({"role": "user", "content": content_blocks}) # Ограничиваем историю (оставляем последние MAX_HISTORY сообщений) if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] payload = { "model": MODEL, "messages": chat_history[chat_id], "temperature": 0.7, "max_tokens": 4096, "stream": False, "ttl": 300, } try: async with aiohttp.ClientSession() as session: async with session.post(URL, json=payload) as resp: if resp.status != 200: error_text = await resp.text() await message.reply( f"❌ Ошибка LM Studio: {resp.status} {error_text}" ) return data = await resp.json() reply_text = data["choices"][0]["message"]["content"] # Сохраняем ответ ассистента в историю chat_history[chat_id].append( { "role": "assistant", "content": [{"type": "text", "text": reply_text}], } ) # Ограничиваем снова (чтобы не разрасталось) if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] # Делим сообщение на части по 4000 символов MAX_LEN = 4000 for i in range(0, len(reply_text), MAX_LEN): chunk = reply_text[i:i + MAX_LEN] msg = await bot.send_message(chat_id=-1003038389942, text=f"{chunk}") save_message(msg.chat.id, msg.message_id) except Exception as e: logger.error(f"Ошибка при запросе к LM Studio: {e}") await message.reply(f"❌ Ошибка при запросе к LM Studio: {e}") @dp.message(Command("igpt")) @admin_required(0) @saving async def ask_gpt(message: Message): raw_text = message.text or message.caption if not raw_text and not ( message.photo or message.document or message.audio or message.video ): await message.reply( "❌ Укажи ID чата и текст или прикрепи файл/медиа: /igpt <сообщение>" ) return args = raw_text.split(maxsplit=2) if raw_text else [] if len(args) < 2: await message.reply("❌ Укажи ID чата: /igpt <сообщение>") return try: chat_id = int(args[1]) # первый аргумент — ID чата except ValueError: await message.reply("❌ Неверный формат chat_id") return user_prompt = args[2] if len(args) > 2 else "" if chat_id not in chat_history: chat_history[chat_id] = [SYSTEM_PROMPT] content_blocks = [] if user_prompt: content_blocks.append({"type": "text", "text": user_prompt}) # Фото → base64 → image_url if message.photo: photo = message.photo[-1] file = await bot.get_file(photo.file_id) file_bytes = await bot.download_file(file.file_path) image_b64 = base64.b64encode(file_bytes.read()).decode("utf-8") content_blocks.append( { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}, } ) if not content_blocks: await message.reply("❗ Укажи текст или прикрепи фото") return # Добавляем новое сообщение в историю chat_history[chat_id].append({"role": "user", "content": content_blocks}) # Ограничиваем историю (оставляем последние MAX_HISTORY сообщений) if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] payload = { "model": MODEL, "messages": chat_history[chat_id], "temperature": 0.7, "max_tokens": 4096, "stream": False, "ttl": 300, } try: async with aiohttp.ClientSession() as session: async with session.post(URL, json=payload) as resp: if resp.status != 200: error_text = await resp.text() await message.reply( f"❌ Ошибка LM Studio: {resp.status} {error_text}" ) return data = await resp.json() reply_text = data["choices"][0]["message"]["content"] # Сохраняем ответ ассистента в историю chat_history[chat_id].append( { "role": "assistant", "content": [{"type": "text", "text": reply_text}], } ) # Ограничиваем снова (чтобы не разрасталось) if len(chat_history[chat_id]) > MAX_HISTORY: chat_history[chat_id] = chat_history[chat_id][-MAX_HISTORY:] # Делим сообщение на части по 4000 символов MAX_LEN = 4000 for i in range(0, len(reply_text), MAX_LEN): chunk = reply_text[i:i + MAX_LEN] msg = await bot.send_message(chat_id=chat_id, text=chunk) save_message(msg.chat.id, msg.message_id) except Exception as e: logger.error(f"Ошибка при запросе к LM Studio: {e}") await message.reply(f"❌ Ошибка при запросе к LM Studio: {e}") @dp.message(Command("clear")) async def clear(message: Message): chat_history.pop(message.chat.id, None)