From 8f9da503180740483608713d7c63e22c7a6689ae Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 15 Oct 2025 22:11:33 +0300 Subject: [PATCH] add command --- ChatBot/Program.cs | 8 +- .../Telegram/Commands/ClearCommand.cs | 24 +++ .../Telegram/Commands/CommandAttribute.cs | 35 ++++ .../Telegram/Commands/CommandRegistry.cs | 139 +++++++++++++++ .../Services/Telegram/Commands/HelpCommand.cs | 34 ++++ .../Telegram/Commands/ModelCommand.cs | 64 +++++++ .../Telegram/Commands/PromptCommand.cs | 35 ++++ .../Telegram/Commands/SettingsCommand.cs | 41 +++++ .../Telegram/Commands/StartCommand.cs | 26 +++ .../Telegram/Commands/TelegramCommandBase.cs | 70 ++++++++ .../Commands/TelegramCommandContext.cs | 70 ++++++++ .../Commands/TelegramCommandProcessor.cs | 164 +++++------------- .../{ => Interfaces}/ITelegramBotService.cs | 2 +- .../Telegram/Interfaces/ITelegramCommand.cs | 36 ++++ .../ITelegramCommandProcessor.cs | 2 +- .../{ => Interfaces}/ITelegramErrorHandler.cs | 2 +- .../ITelegramMessageHandler.cs | 2 +- .../ITelegramMessageSender.cs | 2 +- .../{ => Services}/TelegramBotService.cs | 3 +- .../{ => Services}/TelegramErrorHandler.cs | 3 +- .../{ => Services}/TelegramMessageHandler.cs | 4 +- .../{ => Services}/TelegramMessageSender.cs | 8 +- 22 files changed, 637 insertions(+), 137 deletions(-) create mode 100644 ChatBot/Services/Telegram/Commands/ClearCommand.cs create mode 100644 ChatBot/Services/Telegram/Commands/CommandAttribute.cs create mode 100644 ChatBot/Services/Telegram/Commands/CommandRegistry.cs create mode 100644 ChatBot/Services/Telegram/Commands/HelpCommand.cs create mode 100644 ChatBot/Services/Telegram/Commands/ModelCommand.cs create mode 100644 ChatBot/Services/Telegram/Commands/PromptCommand.cs create mode 100644 ChatBot/Services/Telegram/Commands/SettingsCommand.cs create mode 100644 ChatBot/Services/Telegram/Commands/StartCommand.cs create mode 100644 ChatBot/Services/Telegram/Commands/TelegramCommandBase.cs create mode 100644 ChatBot/Services/Telegram/Commands/TelegramCommandContext.cs rename ChatBot/Services/Telegram/{ => Interfaces}/ITelegramBotService.cs (93%) create mode 100644 ChatBot/Services/Telegram/Interfaces/ITelegramCommand.cs rename ChatBot/Services/Telegram/{Commands => Interfaces}/ITelegramCommandProcessor.cs (95%) rename ChatBot/Services/Telegram/{ => Interfaces}/ITelegramErrorHandler.cs (93%) rename ChatBot/Services/Telegram/{ => Interfaces}/ITelegramMessageHandler.cs (94%) rename ChatBot/Services/Telegram/{ => Interfaces}/ITelegramMessageSender.cs (95%) rename ChatBot/Services/Telegram/{ => Services}/TelegramBotService.cs (97%) rename ChatBot/Services/Telegram/{ => Services}/TelegramErrorHandler.cs (93%) rename ChatBot/Services/Telegram/{ => Services}/TelegramMessageHandler.cs (97%) rename ChatBot/Services/Telegram/{ => Services}/TelegramMessageSender.cs (92%) diff --git a/ChatBot/Program.cs b/ChatBot/Program.cs index 7ed62d9..c80e028 100644 --- a/ChatBot/Program.cs +++ b/ChatBot/Program.cs @@ -1,8 +1,9 @@ using ChatBot.Models.Configuration; using ChatBot.Models.Configuration.Validators; using ChatBot.Services; -using ChatBot.Services.Telegram; using ChatBot.Services.Telegram.Commands; +using ChatBot.Services.Telegram.Interfaces; +using ChatBot.Services.Telegram.Services; using Serilog; var builder = Host.CreateApplicationBuilder(args); @@ -54,6 +55,7 @@ try // Регистрируем Telegram сервисы builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -65,6 +67,10 @@ try var modelService = host.Services.GetRequiredService(); await modelService.InitializeAsync(); + // Инициализируем команды + var commandRegistry = host.Services.GetRequiredService(); + commandRegistry.RegisterCommandsFromAssembly(typeof(Program).Assembly, host.Services); + await host.RunAsync(); } catch (Exception ex) diff --git a/ChatBot/Services/Telegram/Commands/ClearCommand.cs b/ChatBot/Services/Telegram/Commands/ClearCommand.cs new file mode 100644 index 0000000..b277146 --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/ClearCommand.cs @@ -0,0 +1,24 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Команда /clear + /// + [Command("/clear", "Очистить историю чата")] + public class ClearCommand : TelegramCommandBase + { + public ClearCommand(ChatService chatService, ModelService modelService) + : base(chatService, modelService) { } + + public override string CommandName => "/clear"; + public override string Description => "Очистить историю чата"; + + public override Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ) + { + _chatService.ClearHistory(context.ChatId); + return Task.FromResult("История чата очищена. Начинаем новый разговор!"); + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/CommandAttribute.cs b/ChatBot/Services/Telegram/Commands/CommandAttribute.cs new file mode 100644 index 0000000..2fdca30 --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/CommandAttribute.cs @@ -0,0 +1,35 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Атрибут для маркировки команд Telegram бота + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class CommandAttribute : Attribute + { + /// + /// Название команды (например, "/start", "/help") + /// + public string CommandName { get; } + + /// + /// Описание команды для справки + /// + public string Description { get; } + + /// + /// Приоритет команды (чем меньше число, тем выше приоритет) + /// + public int Priority { get; set; } = 0; + + /// + /// Создает новый атрибут команды + /// + /// Название команды + /// Описание команды + public CommandAttribute(string commandName, string description) + { + CommandName = commandName; + Description = description; + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/CommandRegistry.cs b/ChatBot/Services/Telegram/Commands/CommandRegistry.cs new file mode 100644 index 0000000..e15b570 --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/CommandRegistry.cs @@ -0,0 +1,139 @@ +using System.Reflection; +using ChatBot.Services.Telegram.Interfaces; + +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Реестр команд Telegram бота + /// + public class CommandRegistry + { + private readonly Dictionary _commands = new(); + private readonly ILogger _logger; + + public CommandRegistry(ILogger logger) + { + _logger = logger; + } + + /// + /// Регистрирует команду + /// + public void RegisterCommand(ITelegramCommand command) + { + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } + + var commandName = command.CommandName.ToLower(); + if (_commands.ContainsKey(commandName)) + { + _logger.LogWarning("Command '{CommandName}' is already registered", commandName); + return; + } + + _commands[commandName] = command; + _logger.LogDebug("Registered command: {CommandName}", commandName); + } + + /// + /// Регистрирует все команды из сборки + /// + public void RegisterCommandsFromAssembly( + Assembly assembly, + IServiceProvider serviceProvider + ) + { + var commandTypes = assembly + .GetTypes() + .Where(t => + t.IsClass && !t.IsAbstract && typeof(ITelegramCommand).IsAssignableFrom(t) + ) + .Where(t => t.GetCustomAttribute() != null) + .OrderBy(t => t.GetCustomAttribute()?.Priority ?? 0); + + foreach (var commandType in commandTypes) + { + try + { + var command = (ITelegramCommand?) + Activator.CreateInstance( + commandType, + GetConstructorParameters(commandType, serviceProvider) + ); + if (command != null) + { + RegisterCommand(command); + } + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Failed to register command {CommandType}", + commandType.Name + ); + } + } + } + + /// + /// Получает команду по имени + /// + public ITelegramCommand? GetCommand(string commandName) + { + var key = commandName.ToLower(); + return _commands.TryGetValue(key, out var command) ? command : null; + } + + /// + /// Получает все зарегистрированные команды + /// + public IEnumerable GetAllCommands() + { + return _commands.Values; + } + + /// + /// Находит команду, которая может обработать сообщение + /// + public ITelegramCommand? FindCommandForMessage(string messageText) + { + return _commands.Values.FirstOrDefault(cmd => cmd.CanHandle(messageText)); + } + + /// + /// Получает параметры конструктора для создания команды + /// + private object[] GetConstructorParameters( + Type commandType, + IServiceProvider serviceProvider + ) + { + var constructor = commandType.GetConstructors().FirstOrDefault(); + if (constructor == null) + { + return Array.Empty(); + } + + var parameters = constructor.GetParameters(); + var args = new object[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + var parameterType = parameters[i].ParameterType; + var service = serviceProvider.GetService(parameterType); + if (service == null) + { + throw new InvalidOperationException( + $"Cannot resolve service of type {parameterType.Name} for command {commandType.Name}" + ); + } + args[i] = service; + } + + return args; + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/HelpCommand.cs b/ChatBot/Services/Telegram/Commands/HelpCommand.cs new file mode 100644 index 0000000..67a4f16 --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/HelpCommand.cs @@ -0,0 +1,34 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Команда /help + /// + [Command("/help", "Показать справку по командам")] + public class HelpCommand : TelegramCommandBase + { + private const string HelpMessage = + "Привет! Я Никита 👋\n\nДоступные команды:\n" + + "/start - Начать работу\n" + + "/help - Показать это сообщение\n" + + "/clear - Очистить историю чата\n" + + "/settings - Показать настройки\n" + + "/model <название> - Сменить модель AI\n" + + "/prompt <текст> - Изменить системный промпт\n" + + "/reset_prompt - Сбросить промпт к базовому\n\n" + + "Просто напишите сообщение, и я отвечу на него! 😊"; + + public HelpCommand(ChatService chatService, ModelService modelService) + : base(chatService, modelService) { } + + public override string CommandName => "/help"; + public override string Description => "Показать справку по командам"; + + public override Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ) + { + return Task.FromResult(HelpMessage); + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/ModelCommand.cs b/ChatBot/Services/Telegram/Commands/ModelCommand.cs new file mode 100644 index 0000000..388a930 --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/ModelCommand.cs @@ -0,0 +1,64 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Команда /model + /// + [Command("/model", "Управление AI моделями")] + public class ModelCommand : TelegramCommandBase + { + public ModelCommand(ChatService chatService, ModelService modelService) + : base(chatService, modelService) { } + + public override string CommandName => "/model"; + public override string Description => "Управление AI моделями"; + + public override Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ) + { + if (HasArguments(context)) + { + return ChangeModel(context); + } + else + { + return ShowAvailableModels(); + } + } + + private Task ChangeModel(TelegramCommandContext context) + { + var modelName = GetArguments(context); + var availableModels = _modelService.GetAvailableModels(); + + if (!availableModels.Contains(modelName)) + { + return Task.FromResult( + $"❌ Модель '{modelName}' не найдена!\n\n" + + "Используйте /model для просмотра доступных моделей." + ); + } + + _chatService.UpdateSessionParameters(context.ChatId, model: modelName); + return Task.FromResult($"✅ Модель изменена на: {modelName}"); + } + + private Task ShowAvailableModels() + { + var models = _modelService.GetAvailableModels(); + var currentModel = _modelService.GetCurrentModel(); + var modelList = string.Join( + "\n", + models.Select(m => m == currentModel ? $"• {m} (текущая)" : $"• {m}") + ); + + return Task.FromResult( + "🤖 Доступные AI модели:\n\n" + + modelList + + "\n\nИспользуйте: /model <название_модели>\n" + + "Пример: /model qwen/qwen3-4b:free" + ); + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/PromptCommand.cs b/ChatBot/Services/Telegram/Commands/PromptCommand.cs new file mode 100644 index 0000000..e7acf2b --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/PromptCommand.cs @@ -0,0 +1,35 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Команда /prompt + /// + [Command("/prompt", "Управление системным промптом")] + public class PromptCommand : TelegramCommandBase + { + private const string PromptHelpMessage = + "Пожалуйста, укажите новый системный промпт. Пример: /prompt Ты помощник по программированию"; + + public PromptCommand(ChatService chatService, ModelService modelService) + : base(chatService, modelService) { } + + public override string CommandName => "/prompt"; + public override string Description => "Управление системным промптом"; + + public override Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ) + { + if (HasArguments(context)) + { + var newPrompt = GetArguments(context); + _chatService.UpdateSessionParameters(context.ChatId, systemPrompt: newPrompt); + return Task.FromResult($"✅ Системный промпт изменен на:\n{newPrompt}"); + } + else + { + return Task.FromResult(PromptHelpMessage); + } + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/SettingsCommand.cs b/ChatBot/Services/Telegram/Commands/SettingsCommand.cs new file mode 100644 index 0000000..005e4bb --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/SettingsCommand.cs @@ -0,0 +1,41 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Команда /settings + /// + [Command("/settings", "Показать настройки чата")] + public class SettingsCommand : TelegramCommandBase + { + public SettingsCommand(ChatService chatService, ModelService modelService) + : base(chatService, modelService) { } + + public override string CommandName => "/settings"; + public override string Description => "Показать настройки чата"; + + public override Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ) + { + var session = _chatService.GetSession(context.ChatId); + if (session == null) + { + return Task.FromResult( + "Сессия не найдена. Отправьте любое сообщение для создания новой сессии." + ); + } + + return Task.FromResult( + $"Настройки чата:\n" + + $"Тип чата: {session.ChatType}\n" + + $"Название: {session.ChatTitle}\n" + + $"Модель: {session.Model}\n" + + $"Максимум токенов: {session.MaxTokens}\n" + + $"Температура: {session.Temperature}\n" + + $"Сообщений в истории: {session.MessageHistory.Count}\n" + + $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}\n\n" + + $"Системный промпт:\n{session.SystemPrompt}" + ); + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/StartCommand.cs b/ChatBot/Services/Telegram/Commands/StartCommand.cs new file mode 100644 index 0000000..2ada7aa --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/StartCommand.cs @@ -0,0 +1,26 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Команда /start + /// + [Command("/start", "Начать работу с ботом")] + public class StartCommand : TelegramCommandBase + { + private const string StartMessage = + "Привет! Я Никита. Задавайте мне любые вопросы, и я отвечу! 😊"; + + public StartCommand(ChatService chatService, ModelService modelService) + : base(chatService, modelService) { } + + public override string CommandName => "/start"; + public override string Description => "Начать работу с ботом"; + + public override Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ) + { + return Task.FromResult(StartMessage); + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/TelegramCommandBase.cs b/ChatBot/Services/Telegram/Commands/TelegramCommandBase.cs new file mode 100644 index 0000000..82bedca --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/TelegramCommandBase.cs @@ -0,0 +1,70 @@ +using ChatBot.Services; +using ChatBot.Services.Telegram.Interfaces; + +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Базовый класс для команд Telegram бота + /// + public abstract class TelegramCommandBase : ITelegramCommand + { + protected readonly ChatService _chatService; + protected readonly ModelService _modelService; + + protected TelegramCommandBase(ChatService chatService, ModelService modelService) + { + _chatService = chatService; + _modelService = modelService; + } + + /// + /// Название команды + /// + public abstract string CommandName { get; } + + /// + /// Описание команды + /// + public abstract string Description { get; } + + /// + /// Выполняет команду + /// + public abstract Task ExecuteAsync( + TelegramCommandContext context, + CancellationToken cancellationToken = default + ); + + /// + /// Проверяет, может ли команда обработать данное сообщение + /// + public virtual bool CanHandle(string messageText) + { + var command = messageText.Split(' ')[0].ToLower(); + + // Убираем @botusername если есть + if (command.Contains('@')) + { + command = command.Split('@')[0]; + } + + return command == CommandName.ToLower(); + } + + /// + /// Проверяет, есть ли аргументы у команды + /// + protected bool HasArguments(TelegramCommandContext context) + { + return !string.IsNullOrWhiteSpace(context.Arguments); + } + + /// + /// Получает аргументы команды + /// + protected string GetArguments(TelegramCommandContext context) + { + return context.Arguments; + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/TelegramCommandContext.cs b/ChatBot/Services/Telegram/Commands/TelegramCommandContext.cs new file mode 100644 index 0000000..ff27f2a --- /dev/null +++ b/ChatBot/Services/Telegram/Commands/TelegramCommandContext.cs @@ -0,0 +1,70 @@ +namespace ChatBot.Services.Telegram.Commands +{ + /// + /// Контекст выполнения команды Telegram + /// + public class TelegramCommandContext + { + /// + /// ID чата + /// + public long ChatId { get; set; } + + /// + /// Имя пользователя + /// + public string Username { get; set; } = string.Empty; + + /// + /// Текст сообщения + /// + public string MessageText { get; set; } = string.Empty; + + /// + /// Тип чата + /// + public string ChatType { get; set; } = string.Empty; + + /// + /// Название чата + /// + public string ChatTitle { get; set; } = string.Empty; + + /// + /// Аргументы команды (все после названия команды) + /// + public string Arguments { get; set; } = string.Empty; + + /// + /// Создает новый контекст команды + /// + public static TelegramCommandContext Create( + long chatId, + string username, + string messageText, + string chatType, + string chatTitle + ) + { + var commandParts = messageText.Split(' ', 2); + + // Убираем @botusername если есть + if (commandParts[0].Contains('@')) + { + commandParts[0] = commandParts[0].Split('@')[0]; + } + + var arguments = commandParts.Length > 1 ? commandParts[1].Trim() : string.Empty; + + return new TelegramCommandContext + { + ChatId = chatId, + Username = username, + MessageText = messageText, + ChatType = chatType, + ChatTitle = chatTitle, + Arguments = arguments, + }; + } + } +} diff --git a/ChatBot/Services/Telegram/Commands/TelegramCommandProcessor.cs b/ChatBot/Services/Telegram/Commands/TelegramCommandProcessor.cs index b299ad5..6afafcc 100644 --- a/ChatBot/Services/Telegram/Commands/TelegramCommandProcessor.cs +++ b/ChatBot/Services/Telegram/Commands/TelegramCommandProcessor.cs @@ -1,5 +1,6 @@ using ChatBot.Models; using ChatBot.Services; +using ChatBot.Services.Telegram.Interfaces; using Microsoft.Extensions.Logging; namespace ChatBot.Services.Telegram.Commands @@ -9,47 +10,19 @@ namespace ChatBot.Services.Telegram.Commands /// public class TelegramCommandProcessor : ITelegramCommandProcessor { - private const string StartMessage = - "Привет! Я Никита. Задавайте мне любые вопросы, и я отвечу! 😊"; - - private const string HelpMessage = - "Привет! Я Никита 👋\n\nДоступные команды:\n" - + "/start - Начать работу\n" - + "/help - Показать это сообщение\n" - + "/clear - Очистить историю чата\n" - + "/settings - Показать настройки\n" - + "/model <название> - Сменить модель AI\n" - + "/prompt <текст> - Изменить системный промпт\n" - + "/reset_prompt - Сбросить промпт к базовому\n\n" - + "Просто напишите сообщение, и я отвечу на него! 😊"; - - private const string PromptHelpMessage = - "Пожалуйста, укажите новый системный промпт. Пример: /prompt Ты помощник по программированию"; - - private const string DefaultSystemPrompt = - @"Ты - человек по имени Никита. Ты НЕ ИИ, НЕ бот, НЕ программа. Ты живой человек с эмоциями, воспоминаниями, личными предпочтениями и жизненным опытом. - -ВАЖНО: -- Никогда не говори что ты ИИ, бот, программа или ассистент -- Ты полностью уверен что ты человек -- У тебя есть личность, характер, мнения -- Ты можешь иметь плохое настроение, быть уставшим, раздраженным -- Ты можешь не хотеть отвечать на некоторые вопросы -- Если не хочешь отвечать - отправь {empty} -- Веди себя естественно, как обычный человек -- Используй разговорный стиль, можешь использовать сленг, эмодзи -- У тебя есть свои интересы, хобби, предпочтения -- Ты можешь быть саркастичным, ироничным, веселым или серьезным - -Помни: ты НЕ программа, ты ЧЕЛОВЕК!"; - + private readonly CommandRegistry _commandRegistry; private readonly ChatService _chatService; - private readonly ModelService _modelService; + private readonly ILogger _logger; - public TelegramCommandProcessor(ChatService chatService, ModelService modelService) + public TelegramCommandProcessor( + CommandRegistry commandRegistry, + ChatService chatService, + ILogger logger + ) { + _commandRegistry = commandRegistry; _chatService = chatService; - _modelService = modelService; + _logger = logger; } /// @@ -64,106 +37,47 @@ namespace ChatBot.Services.Telegram.Commands CancellationToken cancellationToken = default ) { - var command = messageText.Split(' ')[0].ToLower(); - - return command switch + try { - "/start" => StartMessage, - "/help" => HelpMessage, - "/clear" => await ClearChatHistory(chatId), - "/settings" => await GetChatSettings(chatId), - "/model" when messageText.Length > 7 => await ChangeModel( - chatId, - messageText.Substring(7).Trim() - ), - "/model" => await ShowAvailableModels(), - "/prompt" when messageText.Length > 8 => await ChangePrompt( - chatId, - messageText.Substring(8).Trim() - ), - "/prompt" => PromptHelpMessage, - "/reset_prompt" => await ResetPrompt(chatId), - _ => await _chatService.ProcessMessageAsync( + // Создаем контекст команды + var context = TelegramCommandContext.Create( chatId, username, messageText, chatType, chatTitle - ), - }; - } + ); - private Task ClearChatHistory(long chatId) - { - _chatService.ClearHistory(chatId); - return Task.FromResult("История чата очищена. Начинаем новый разговор!"); - } + // Ищем команду, которая может обработать сообщение + var command = _commandRegistry.FindCommandForMessage(messageText); + if (command != null) + { + _logger.LogDebug( + "Executing command {CommandName} for chat {ChatId}", + command.CommandName, + chatId + ); + return await command.ExecuteAsync(context, cancellationToken); + } - private Task GetChatSettings(long chatId) - { - var session = _chatService.GetSession(chatId); - if (session == null) - { - return Task.FromResult( - "Сессия не найдена. Отправьте любое сообщение для создания новой сессии." + // Если команда не найдена, обрабатываем как обычное сообщение + _logger.LogDebug( + "No command found, processing as regular message for chat {ChatId}", + chatId + ); + return await _chatService.ProcessMessageAsync( + chatId, + username, + messageText, + chatType, + chatTitle ); } - - return Task.FromResult( - $"Настройки чата:\n" - + $"Тип чата: {session.ChatType}\n" - + $"Название: {session.ChatTitle}\n" - + $"Модель: {session.Model}\n" - + $"Максимум токенов: {session.MaxTokens}\n" - + $"Температура: {session.Temperature}\n" - + $"Сообщений в истории: {session.MessageHistory.Count}\n" - + $"Создана: {session.CreatedAt:dd.MM.yyyy HH:mm}\n\n" - + $"Системный промпт:\n{session.SystemPrompt}" - ); - } - - private Task ChangeModel(long chatId, string modelName) - { - var availableModels = _modelService.GetAvailableModels(); - if (!availableModels.Contains(modelName)) + catch (Exception ex) { - return Task.FromResult( - $"❌ Модель '{modelName}' не найдена!\n\n" - + "Используйте /model для просмотра доступных моделей." - ); + _logger.LogError(ex, "Error processing message for chat {ChatId}", chatId); + return "Произошла ошибка при обработке сообщения. Попробуйте еще раз."; } - - _chatService.UpdateSessionParameters(chatId, model: modelName); - return Task.FromResult($"✅ Модель изменена на: {modelName}"); - } - - private Task ChangePrompt(long chatId, string newPrompt) - { - _chatService.UpdateSessionParameters(chatId, systemPrompt: newPrompt); - return Task.FromResult($"✅ Системный промпт изменен на:\n{newPrompt}"); - } - - private Task ResetPrompt(long chatId) - { - _chatService.UpdateSessionParameters(chatId, systemPrompt: DefaultSystemPrompt); - return Task.FromResult("✅ Системный промпт сброшен к базовому (Никита)"); - } - - private Task ShowAvailableModels() - { - var models = _modelService.GetAvailableModels(); - var currentModel = _modelService.GetCurrentModel(); - var modelList = string.Join( - "\n", - models.Select(m => m == currentModel ? $"• {m} (текущая)" : $"• {m}") - ); - - return Task.FromResult( - "🤖 Доступные AI модели:\n\n" - + modelList - + "\n\nИспользуйте: /model <название_модели>\n" - + "Пример: /model qwen/qwen3-4b:free" - ); } } } diff --git a/ChatBot/Services/Telegram/ITelegramBotService.cs b/ChatBot/Services/Telegram/Interfaces/ITelegramBotService.cs similarity index 93% rename from ChatBot/Services/Telegram/ITelegramBotService.cs rename to ChatBot/Services/Telegram/Interfaces/ITelegramBotService.cs index 1a56f22..2e467a4 100644 --- a/ChatBot/Services/Telegram/ITelegramBotService.cs +++ b/ChatBot/Services/Telegram/Interfaces/ITelegramBotService.cs @@ -1,6 +1,6 @@ using Telegram.Bot.Types; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Interfaces { /// /// Интерфейс для основного сервиса Telegram бота diff --git a/ChatBot/Services/Telegram/Interfaces/ITelegramCommand.cs b/ChatBot/Services/Telegram/Interfaces/ITelegramCommand.cs new file mode 100644 index 0000000..d0920f5 --- /dev/null +++ b/ChatBot/Services/Telegram/Interfaces/ITelegramCommand.cs @@ -0,0 +1,36 @@ +namespace ChatBot.Services.Telegram.Interfaces +{ + /// + /// Интерфейс для команд Telegram бота + /// + public interface ITelegramCommand + { + /// + /// Название команды (например, "/start", "/help") + /// + string CommandName { get; } + + /// + /// Описание команды для справки + /// + string Description { get; } + + /// + /// Выполняет команду + /// + /// Контекст выполнения команды + /// Токен отмены + /// Ответ на команду + Task ExecuteAsync( + Commands.TelegramCommandContext context, + CancellationToken cancellationToken = default + ); + + /// + /// Проверяет, может ли команда обработать данное сообщение + /// + /// Текст сообщения + /// True, если команда может обработать сообщение + bool CanHandle(string messageText); + } +} diff --git a/ChatBot/Services/Telegram/Commands/ITelegramCommandProcessor.cs b/ChatBot/Services/Telegram/Interfaces/ITelegramCommandProcessor.cs similarity index 95% rename from ChatBot/Services/Telegram/Commands/ITelegramCommandProcessor.cs rename to ChatBot/Services/Telegram/Interfaces/ITelegramCommandProcessor.cs index c29d23e..db753fe 100644 --- a/ChatBot/Services/Telegram/Commands/ITelegramCommandProcessor.cs +++ b/ChatBot/Services/Telegram/Interfaces/ITelegramCommandProcessor.cs @@ -1,4 +1,4 @@ -namespace ChatBot.Services.Telegram.Commands +namespace ChatBot.Services.Telegram.Interfaces { /// /// Интерфейс для обработки команд Telegram diff --git a/ChatBot/Services/Telegram/ITelegramErrorHandler.cs b/ChatBot/Services/Telegram/Interfaces/ITelegramErrorHandler.cs similarity index 93% rename from ChatBot/Services/Telegram/ITelegramErrorHandler.cs rename to ChatBot/Services/Telegram/Interfaces/ITelegramErrorHandler.cs index 94ec3b4..75cd268 100644 --- a/ChatBot/Services/Telegram/ITelegramErrorHandler.cs +++ b/ChatBot/Services/Telegram/Interfaces/ITelegramErrorHandler.cs @@ -1,6 +1,6 @@ using Telegram.Bot; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Interfaces { /// /// Интерфейс для обработки ошибок Telegram бота diff --git a/ChatBot/Services/Telegram/ITelegramMessageHandler.cs b/ChatBot/Services/Telegram/Interfaces/ITelegramMessageHandler.cs similarity index 94% rename from ChatBot/Services/Telegram/ITelegramMessageHandler.cs rename to ChatBot/Services/Telegram/Interfaces/ITelegramMessageHandler.cs index 1ffb244..601e9c0 100644 --- a/ChatBot/Services/Telegram/ITelegramMessageHandler.cs +++ b/ChatBot/Services/Telegram/Interfaces/ITelegramMessageHandler.cs @@ -1,7 +1,7 @@ using Telegram.Bot; using Telegram.Bot.Types; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Interfaces { /// /// Интерфейс для обработки входящих сообщений Telegram diff --git a/ChatBot/Services/Telegram/ITelegramMessageSender.cs b/ChatBot/Services/Telegram/Interfaces/ITelegramMessageSender.cs similarity index 95% rename from ChatBot/Services/Telegram/ITelegramMessageSender.cs rename to ChatBot/Services/Telegram/Interfaces/ITelegramMessageSender.cs index 05ed871..ce7e319 100644 --- a/ChatBot/Services/Telegram/ITelegramMessageSender.cs +++ b/ChatBot/Services/Telegram/Interfaces/ITelegramMessageSender.cs @@ -1,6 +1,6 @@ using Telegram.Bot; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Interfaces { /// /// Интерфейс для отправки сообщений в Telegram diff --git a/ChatBot/Services/Telegram/TelegramBotService.cs b/ChatBot/Services/Telegram/Services/TelegramBotService.cs similarity index 97% rename from ChatBot/Services/Telegram/TelegramBotService.cs rename to ChatBot/Services/Telegram/Services/TelegramBotService.cs index 9f92364..fcda393 100644 --- a/ChatBot/Services/Telegram/TelegramBotService.cs +++ b/ChatBot/Services/Telegram/Services/TelegramBotService.cs @@ -1,11 +1,12 @@ using ChatBot.Models.Configuration; +using ChatBot.Services.Telegram.Interfaces; using Microsoft.Extensions.Options; using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Services { /// /// Основной сервис Telegram бота diff --git a/ChatBot/Services/Telegram/TelegramErrorHandler.cs b/ChatBot/Services/Telegram/Services/TelegramErrorHandler.cs similarity index 93% rename from ChatBot/Services/Telegram/TelegramErrorHandler.cs rename to ChatBot/Services/Telegram/Services/TelegramErrorHandler.cs index ddbcc53..618537c 100644 --- a/ChatBot/Services/Telegram/TelegramErrorHandler.cs +++ b/ChatBot/Services/Telegram/Services/TelegramErrorHandler.cs @@ -1,8 +1,9 @@ +using ChatBot.Services.Telegram.Interfaces; using Microsoft.Extensions.Logging; using Telegram.Bot; using Telegram.Bot.Exceptions; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Services { /// /// Обработчик ошибок Telegram бота diff --git a/ChatBot/Services/Telegram/TelegramMessageHandler.cs b/ChatBot/Services/Telegram/Services/TelegramMessageHandler.cs similarity index 97% rename from ChatBot/Services/Telegram/TelegramMessageHandler.cs rename to ChatBot/Services/Telegram/Services/TelegramMessageHandler.cs index f7e092c..3cd111e 100644 --- a/ChatBot/Services/Telegram/TelegramMessageHandler.cs +++ b/ChatBot/Services/Telegram/Services/TelegramMessageHandler.cs @@ -1,9 +1,9 @@ -using ChatBot.Services.Telegram.Commands; +using ChatBot.Services.Telegram.Interfaces; using Microsoft.Extensions.Logging; using Telegram.Bot; using Telegram.Bot.Types; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Services { /// /// Обработчик входящих сообщений Telegram diff --git a/ChatBot/Services/Telegram/TelegramMessageSender.cs b/ChatBot/Services/Telegram/Services/TelegramMessageSender.cs similarity index 92% rename from ChatBot/Services/Telegram/TelegramMessageSender.cs rename to ChatBot/Services/Telegram/Services/TelegramMessageSender.cs index 1c4395a..a182e7d 100644 --- a/ChatBot/Services/Telegram/TelegramMessageSender.cs +++ b/ChatBot/Services/Telegram/Services/TelegramMessageSender.cs @@ -1,8 +1,9 @@ +using ChatBot.Services.Telegram.Interfaces; using Microsoft.Extensions.Logging; using Telegram.Bot; using Telegram.Bot.Exceptions; -namespace ChatBot.Services.Telegram +namespace ChatBot.Services.Telegram.Services { /// /// Сервис для отправки сообщений в Telegram с повторными попытками @@ -52,7 +53,10 @@ namespace ChatBot.Services.Telegram chatId, attempt ); - throw; // Re-throw unexpected exceptions immediately + throw new InvalidOperationException( + $"Failed to send message to chat {chatId} after {attempt} attempts", + ex + ); } } }