add command
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
using ChatBot.Models.Configuration;
|
using ChatBot.Models.Configuration;
|
||||||
using ChatBot.Models.Configuration.Validators;
|
using ChatBot.Models.Configuration.Validators;
|
||||||
using ChatBot.Services;
|
using ChatBot.Services;
|
||||||
using ChatBot.Services.Telegram;
|
|
||||||
using ChatBot.Services.Telegram.Commands;
|
using ChatBot.Services.Telegram.Commands;
|
||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
|
using ChatBot.Services.Telegram.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
var builder = Host.CreateApplicationBuilder(args);
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
@@ -54,6 +55,7 @@ try
|
|||||||
// Регистрируем Telegram сервисы
|
// Регистрируем Telegram сервисы
|
||||||
builder.Services.AddSingleton<ITelegramMessageSender, TelegramMessageSender>();
|
builder.Services.AddSingleton<ITelegramMessageSender, TelegramMessageSender>();
|
||||||
builder.Services.AddSingleton<ITelegramErrorHandler, TelegramErrorHandler>();
|
builder.Services.AddSingleton<ITelegramErrorHandler, TelegramErrorHandler>();
|
||||||
|
builder.Services.AddSingleton<CommandRegistry>();
|
||||||
builder.Services.AddSingleton<ITelegramCommandProcessor, TelegramCommandProcessor>();
|
builder.Services.AddSingleton<ITelegramCommandProcessor, TelegramCommandProcessor>();
|
||||||
builder.Services.AddSingleton<ITelegramMessageHandler, TelegramMessageHandler>();
|
builder.Services.AddSingleton<ITelegramMessageHandler, TelegramMessageHandler>();
|
||||||
builder.Services.AddSingleton<ITelegramBotService, TelegramBotService>();
|
builder.Services.AddSingleton<ITelegramBotService, TelegramBotService>();
|
||||||
@@ -65,6 +67,10 @@ try
|
|||||||
var modelService = host.Services.GetRequiredService<ModelService>();
|
var modelService = host.Services.GetRequiredService<ModelService>();
|
||||||
await modelService.InitializeAsync();
|
await modelService.InitializeAsync();
|
||||||
|
|
||||||
|
// Инициализируем команды
|
||||||
|
var commandRegistry = host.Services.GetRequiredService<CommandRegistry>();
|
||||||
|
commandRegistry.RegisterCommandsFromAssembly(typeof(Program).Assembly, host.Services);
|
||||||
|
|
||||||
await host.RunAsync();
|
await host.RunAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
24
ChatBot/Services/Telegram/Commands/ClearCommand.cs
Normal file
24
ChatBot/Services/Telegram/Commands/ClearCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Команда /clear
|
||||||
|
/// </summary>
|
||||||
|
[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<string> ExecuteAsync(
|
||||||
|
TelegramCommandContext context,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_chatService.ClearHistory(context.ChatId);
|
||||||
|
return Task.FromResult("История чата очищена. Начинаем новый разговор!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
ChatBot/Services/Telegram/Commands/CommandAttribute.cs
Normal file
35
ChatBot/Services/Telegram/Commands/CommandAttribute.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Атрибут для маркировки команд Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||||
|
public class CommandAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Название команды (например, "/start", "/help")
|
||||||
|
/// </summary>
|
||||||
|
public string CommandName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Описание команды для справки
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Приоритет команды (чем меньше число, тем выше приоритет)
|
||||||
|
/// </summary>
|
||||||
|
public int Priority { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает новый атрибут команды
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandName">Название команды</param>
|
||||||
|
/// <param name="description">Описание команды</param>
|
||||||
|
public CommandAttribute(string commandName, string description)
|
||||||
|
{
|
||||||
|
CommandName = commandName;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
139
ChatBot/Services/Telegram/Commands/CommandRegistry.cs
Normal file
139
ChatBot/Services/Telegram/Commands/CommandRegistry.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Реестр команд Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public class CommandRegistry
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, ITelegramCommand> _commands = new();
|
||||||
|
private readonly ILogger<CommandRegistry> _logger;
|
||||||
|
|
||||||
|
public CommandRegistry(ILogger<CommandRegistry> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Регистрирует команду
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Регистрирует все команды из сборки
|
||||||
|
/// </summary>
|
||||||
|
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<CommandAttribute>() != null)
|
||||||
|
.OrderBy(t => t.GetCustomAttribute<CommandAttribute>()?.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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает команду по имени
|
||||||
|
/// </summary>
|
||||||
|
public ITelegramCommand? GetCommand(string commandName)
|
||||||
|
{
|
||||||
|
var key = commandName.ToLower();
|
||||||
|
return _commands.TryGetValue(key, out var command) ? command : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает все зарегистрированные команды
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<ITelegramCommand> GetAllCommands()
|
||||||
|
{
|
||||||
|
return _commands.Values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Находит команду, которая может обработать сообщение
|
||||||
|
/// </summary>
|
||||||
|
public ITelegramCommand? FindCommandForMessage(string messageText)
|
||||||
|
{
|
||||||
|
return _commands.Values.FirstOrDefault(cmd => cmd.CanHandle(messageText));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает параметры конструктора для создания команды
|
||||||
|
/// </summary>
|
||||||
|
private object[] GetConstructorParameters(
|
||||||
|
Type commandType,
|
||||||
|
IServiceProvider serviceProvider
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var constructor = commandType.GetConstructors().FirstOrDefault();
|
||||||
|
if (constructor == null)
|
||||||
|
{
|
||||||
|
return Array.Empty<object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
ChatBot/Services/Telegram/Commands/HelpCommand.cs
Normal file
34
ChatBot/Services/Telegram/Commands/HelpCommand.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Команда /help
|
||||||
|
/// </summary>
|
||||||
|
[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<string> ExecuteAsync(
|
||||||
|
TelegramCommandContext context,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Task.FromResult(HelpMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
ChatBot/Services/Telegram/Commands/ModelCommand.cs
Normal file
64
ChatBot/Services/Telegram/Commands/ModelCommand.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Команда /model
|
||||||
|
/// </summary>
|
||||||
|
[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<string> ExecuteAsync(
|
||||||
|
TelegramCommandContext context,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (HasArguments(context))
|
||||||
|
{
|
||||||
|
return ChangeModel(context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ShowAvailableModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> 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<string> 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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
ChatBot/Services/Telegram/Commands/PromptCommand.cs
Normal file
35
ChatBot/Services/Telegram/Commands/PromptCommand.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Команда /prompt
|
||||||
|
/// </summary>
|
||||||
|
[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<string> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
ChatBot/Services/Telegram/Commands/SettingsCommand.cs
Normal file
41
ChatBot/Services/Telegram/Commands/SettingsCommand.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Команда /settings
|
||||||
|
/// </summary>
|
||||||
|
[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<string> 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}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
ChatBot/Services/Telegram/Commands/StartCommand.cs
Normal file
26
ChatBot/Services/Telegram/Commands/StartCommand.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Команда /start
|
||||||
|
/// </summary>
|
||||||
|
[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<string> ExecuteAsync(
|
||||||
|
TelegramCommandContext context,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Task.FromResult(StartMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
ChatBot/Services/Telegram/Commands/TelegramCommandBase.cs
Normal file
70
ChatBot/Services/Telegram/Commands/TelegramCommandBase.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using ChatBot.Services;
|
||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
|
|
||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Базовый класс для команд Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public abstract class TelegramCommandBase : ITelegramCommand
|
||||||
|
{
|
||||||
|
protected readonly ChatService _chatService;
|
||||||
|
protected readonly ModelService _modelService;
|
||||||
|
|
||||||
|
protected TelegramCommandBase(ChatService chatService, ModelService modelService)
|
||||||
|
{
|
||||||
|
_chatService = chatService;
|
||||||
|
_modelService = modelService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Название команды
|
||||||
|
/// </summary>
|
||||||
|
public abstract string CommandName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Описание команды
|
||||||
|
/// </summary>
|
||||||
|
public abstract string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет команду
|
||||||
|
/// </summary>
|
||||||
|
public abstract Task<string> ExecuteAsync(
|
||||||
|
TelegramCommandContext context,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, может ли команда обработать данное сообщение
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool CanHandle(string messageText)
|
||||||
|
{
|
||||||
|
var command = messageText.Split(' ')[0].ToLower();
|
||||||
|
|
||||||
|
// Убираем @botusername если есть
|
||||||
|
if (command.Contains('@'))
|
||||||
|
{
|
||||||
|
command = command.Split('@')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return command == CommandName.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, есть ли аргументы у команды
|
||||||
|
/// </summary>
|
||||||
|
protected bool HasArguments(TelegramCommandContext context)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(context.Arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получает аргументы команды
|
||||||
|
/// </summary>
|
||||||
|
protected string GetArguments(TelegramCommandContext context)
|
||||||
|
{
|
||||||
|
return context.Arguments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
ChatBot/Services/Telegram/Commands/TelegramCommandContext.cs
Normal file
70
ChatBot/Services/Telegram/Commands/TelegramCommandContext.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Контекст выполнения команды Telegram
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramCommandContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ID чата
|
||||||
|
/// </summary>
|
||||||
|
public long ChatId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Имя пользователя
|
||||||
|
/// </summary>
|
||||||
|
public string Username { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Текст сообщения
|
||||||
|
/// </summary>
|
||||||
|
public string MessageText { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Тип чата
|
||||||
|
/// </summary>
|
||||||
|
public string ChatType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Название чата
|
||||||
|
/// </summary>
|
||||||
|
public string ChatTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Аргументы команды (все после названия команды)
|
||||||
|
/// </summary>
|
||||||
|
public string Arguments { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создает новый контекст команды
|
||||||
|
/// </summary>
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using ChatBot.Models;
|
using ChatBot.Models;
|
||||||
using ChatBot.Services;
|
using ChatBot.Services;
|
||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram.Commands
|
namespace ChatBot.Services.Telegram.Commands
|
||||||
@@ -9,47 +10,19 @@ namespace ChatBot.Services.Telegram.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TelegramCommandProcessor : ITelegramCommandProcessor
|
public class TelegramCommandProcessor : ITelegramCommandProcessor
|
||||||
{
|
{
|
||||||
private const string StartMessage =
|
private readonly CommandRegistry _commandRegistry;
|
||||||
"Привет! Я Никита. Задавайте мне любые вопросы, и я отвечу! 😊";
|
|
||||||
|
|
||||||
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 ChatService _chatService;
|
private readonly ChatService _chatService;
|
||||||
private readonly ModelService _modelService;
|
private readonly ILogger<TelegramCommandProcessor> _logger;
|
||||||
|
|
||||||
public TelegramCommandProcessor(ChatService chatService, ModelService modelService)
|
public TelegramCommandProcessor(
|
||||||
|
CommandRegistry commandRegistry,
|
||||||
|
ChatService chatService,
|
||||||
|
ILogger<TelegramCommandProcessor> logger
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
_commandRegistry = commandRegistry;
|
||||||
_chatService = chatService;
|
_chatService = chatService;
|
||||||
_modelService = modelService;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -64,106 +37,47 @@ namespace ChatBot.Services.Telegram.Commands
|
|||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var command = messageText.Split(' ')[0].ToLower();
|
try
|
||||||
|
|
||||||
return command switch
|
|
||||||
{
|
{
|
||||||
"/start" => StartMessage,
|
// Создаем контекст команды
|
||||||
"/help" => HelpMessage,
|
var context = TelegramCommandContext.Create(
|
||||||
"/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(
|
|
||||||
chatId,
|
chatId,
|
||||||
username,
|
username,
|
||||||
messageText,
|
messageText,
|
||||||
chatType,
|
chatType,
|
||||||
chatTitle
|
chatTitle
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ClearChatHistory(long chatId)
|
|
||||||
{
|
|
||||||
_chatService.ClearHistory(chatId);
|
|
||||||
return Task.FromResult("История чата очищена. Начинаем новый разговор!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> GetChatSettings(long chatId)
|
|
||||||
{
|
|
||||||
var session = _chatService.GetSession(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}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ChangeModel(long chatId, string modelName)
|
|
||||||
{
|
|
||||||
var availableModels = _modelService.GetAvailableModels();
|
|
||||||
if (!availableModels.Contains(modelName))
|
|
||||||
{
|
|
||||||
return Task.FromResult(
|
|
||||||
$"❌ Модель '{modelName}' не найдена!\n\n"
|
|
||||||
+ "Используйте /model для просмотра доступных моделей."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_chatService.UpdateSessionParameters(chatId, model: modelName);
|
|
||||||
return Task.FromResult($"✅ Модель изменена на: {modelName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ChangePrompt(long chatId, string newPrompt)
|
|
||||||
{
|
|
||||||
_chatService.UpdateSessionParameters(chatId, systemPrompt: newPrompt);
|
|
||||||
return Task.FromResult($"✅ Системный промпт изменен на:\n{newPrompt}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> ResetPrompt(long chatId)
|
|
||||||
{
|
|
||||||
_chatService.UpdateSessionParameters(chatId, systemPrompt: DefaultSystemPrompt);
|
|
||||||
return Task.FromResult("✅ Системный промпт сброшен к базовому (Никита)");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<string> 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"
|
var command = _commandRegistry.FindCommandForMessage(messageText);
|
||||||
+ modelList
|
if (command != null)
|
||||||
+ "\n\nИспользуйте: /model <название_модели>\n"
|
{
|
||||||
+ "Пример: /model qwen/qwen3-4b:free"
|
_logger.LogDebug(
|
||||||
|
"Executing command {CommandName} for chat {ChatId}",
|
||||||
|
command.CommandName,
|
||||||
|
chatId
|
||||||
|
);
|
||||||
|
return await command.ExecuteAsync(context, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если команда не найдена, обрабатываем как обычное сообщение
|
||||||
|
_logger.LogDebug(
|
||||||
|
"No command found, processing as regular message for chat {ChatId}",
|
||||||
|
chatId
|
||||||
|
);
|
||||||
|
return await _chatService.ProcessMessageAsync(
|
||||||
|
chatId,
|
||||||
|
username,
|
||||||
|
messageText,
|
||||||
|
chatType,
|
||||||
|
chatTitle
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error processing message for chat {ChatId}", chatId);
|
||||||
|
return "Произошла ошибка при обработке сообщения. Попробуйте еще раз.";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Telegram.Bot.Types;
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Интерфейс для основного сервиса Telegram бота
|
/// Интерфейс для основного сервиса Telegram бота
|
||||||
36
ChatBot/Services/Telegram/Interfaces/ITelegramCommand.cs
Normal file
36
ChatBot/Services/Telegram/Interfaces/ITelegramCommand.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для команд Telegram бота
|
||||||
|
/// </summary>
|
||||||
|
public interface ITelegramCommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Название команды (например, "/start", "/help")
|
||||||
|
/// </summary>
|
||||||
|
string CommandName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Описание команды для справки
|
||||||
|
/// </summary>
|
||||||
|
string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Выполняет команду
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">Контекст выполнения команды</param>
|
||||||
|
/// <param name="cancellationToken">Токен отмены</param>
|
||||||
|
/// <returns>Ответ на команду</returns>
|
||||||
|
Task<string> ExecuteAsync(
|
||||||
|
Commands.TelegramCommandContext context,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверяет, может ли команда обработать данное сообщение
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageText">Текст сообщения</param>
|
||||||
|
/// <returns>True, если команда может обработать сообщение</returns>
|
||||||
|
bool CanHandle(string messageText);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace ChatBot.Services.Telegram.Commands
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Интерфейс для обработки команд Telegram
|
/// Интерфейс для обработки команд Telegram
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Интерфейс для обработки ошибок Telegram бота
|
/// Интерфейс для обработки ошибок Telegram бота
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Types;
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Интерфейс для обработки входящих сообщений Telegram
|
/// Интерфейс для обработки входящих сообщений Telegram
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Интерфейс для отправки сообщений в Telegram
|
/// Интерфейс для отправки сообщений в Telegram
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using ChatBot.Models.Configuration;
|
using ChatBot.Models.Configuration;
|
||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Polling;
|
using Telegram.Bot.Polling;
|
||||||
using Telegram.Bot.Types;
|
using Telegram.Bot.Types;
|
||||||
using Telegram.Bot.Types.Enums;
|
using Telegram.Bot.Types.Enums;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Основной сервис Telegram бота
|
/// Основной сервис Telegram бота
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Exceptions;
|
using Telegram.Bot.Exceptions;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обработчик ошибок Telegram бота
|
/// Обработчик ошибок Telegram бота
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using ChatBot.Services.Telegram.Commands;
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Types;
|
using Telegram.Bot.Types;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Обработчик входящих сообщений Telegram
|
/// Обработчик входящих сообщений Telegram
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
|
using ChatBot.Services.Telegram.Interfaces;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Exceptions;
|
using Telegram.Bot.Exceptions;
|
||||||
|
|
||||||
namespace ChatBot.Services.Telegram
|
namespace ChatBot.Services.Telegram.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Сервис для отправки сообщений в Telegram с повторными попытками
|
/// Сервис для отправки сообщений в Telegram с повторными попытками
|
||||||
@@ -52,7 +53,10 @@ namespace ChatBot.Services.Telegram
|
|||||||
chatId,
|
chatId,
|
||||||
attempt
|
attempt
|
||||||
);
|
);
|
||||||
throw; // Re-throw unexpected exceptions immediately
|
throw new InvalidOperationException(
|
||||||
|
$"Failed to send message to chat {chatId} after {attempt} attempts",
|
||||||
|
ex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user