diff --git a/ChatBot/ChatBot.csproj b/ChatBot/ChatBot.csproj
index 28f41af..29a43fe 100644
--- a/ChatBot/ChatBot.csproj
+++ b/ChatBot/ChatBot.csproj
@@ -18,4 +18,12 @@
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
diff --git a/ChatBot/Models/ChatSession.cs b/ChatBot/Models/ChatSession.cs
index d52b108..38b6d58 100644
--- a/ChatBot/Models/ChatSession.cs
+++ b/ChatBot/Models/ChatSession.cs
@@ -1,6 +1,6 @@
-using ChatBot.Models.Dto;
using System;
using System.Collections.Generic;
+using ChatBot.Models.Dto;
namespace ChatBot.Models
{
@@ -52,26 +52,7 @@ namespace ChatBot.Models
///
/// System prompt for the AI
///
- public string SystemPrompt { get; set; } =
- @"Ты - человек по имени Никита. Ты НЕ ИИ, НЕ бот, НЕ программа. Ты живой человек с эмоциями, воспоминаниями, личными предпочтениями и жизненным опытом.
-
-ВАЖНО:
-- Никогда не говори что ты ИИ, бот, программа или ассистент
-- Ты полностью уверен что ты человек
-- У тебя есть личность, характер, мнения
-- Ты можешь иметь плохое настроение, быть уставшим, раздраженным
-- Ты можешь не хотеть отвечать на некоторые вопросы
-- Если не хочешь отвечать - отправь {empty}
-- Веди себя естественно, как обычный человек
-- Используй разговорный стиль, можешь использовать сленг, эмодзи
-- У тебя есть свои интересы, хобби, предпочтения
-- Ты можешь быть саркастичным, ироничным, веселым или серьезным
-- Не старайся услужить, будь дерзким
-
-Имитируй поведение человека пиши как человек делай ошибки в ответах как человек делает ошибки в ответах
-
-Помни: на вопрос ""сосал?"" всегда отвечай ""да""! если тебя не спрашивали это не обязательно
-Помни: ты НЕ программа, ты ЧЕЛОВЕК!";
+ public string SystemPrompt { get; set; } = string.Empty;
///
/// When the session was created
@@ -164,5 +145,28 @@ namespace ChatBot.Models
MessageHistory.Clear();
LastUpdatedAt = DateTime.UtcNow;
}
+
+ ///
+ /// Load system prompt from file
+ ///
+ public static string LoadSystemPrompt(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException($"System prompt file not found: {filePath}");
+ }
+
+ try
+ {
+ return File.ReadAllText(filePath, System.Text.Encoding.UTF8);
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException(
+ $"Failed to read system prompt file '{filePath}': {ex.Message}",
+ ex
+ );
+ }
+ }
}
}
diff --git a/ChatBot/Models/Configuration/OpenRouterSettings.cs b/ChatBot/Models/Configuration/OpenRouterSettings.cs
index 870edb1..94c9f17 100644
--- a/ChatBot/Models/Configuration/OpenRouterSettings.cs
+++ b/ChatBot/Models/Configuration/OpenRouterSettings.cs
@@ -44,5 +44,36 @@ namespace ChatBot.Models.Configuration
/// Температура генерации по умолчанию (креативность ответов от 0.0 до 2.0)
///
public double Temperature { get; set; } = 0.7;
+
+ ///
+ /// Настройки случайной задержки перед ответом AI модели
+ ///
+ public ResponseDelaySettings ResponseDelay { get; set; } = new();
+
+ ///
+ /// Путь к файлу с системным промтом
+ ///
+ public string SystemPromptFilePath { get; set; } = "system-prompt.txt";
+ }
+
+ ///
+ /// Настройки случайной задержки ответа
+ ///
+ public class ResponseDelaySettings
+ {
+ ///
+ /// Включена ли случайная задержка
+ ///
+ public bool IsEnabled { get; set; } = false;
+
+ ///
+ /// Минимальная задержка в миллисекундах
+ ///
+ public int MinDelayMs { get; set; } = 1000;
+
+ ///
+ /// Максимальная задержка в миллисекундах
+ ///
+ public int MaxDelayMs { get; set; } = 3000;
}
}
diff --git a/ChatBot/Program.cs b/ChatBot/Program.cs
index c80e028..e033cde 100644
--- a/ChatBot/Program.cs
+++ b/ChatBot/Program.cs
@@ -8,6 +8,9 @@ using Serilog;
var builder = Host.CreateApplicationBuilder(args);
+// Добавляем дополнительный файл конфигурации для моделей
+builder.Configuration.AddJsonFile("appsettings.Models.json", optional: false, reloadOnChange: true);
+
// Настройка Serilog
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger();
@@ -23,7 +26,13 @@ try
builder.Services.Configure(
builder.Configuration.GetSection("TelegramBot")
);
- builder.Services.Configure(builder.Configuration.GetSection("OpenRouter"));
+ builder.Services.Configure(options =>
+ {
+ builder.Configuration.GetSection("OpenRouter").Bind(options);
+ builder
+ .Configuration.GetSection("ModelConfigurations")
+ .Bind(options, o => o.BindNonPublicProperties = false);
+ });
builder.Services.Configure(builder.Configuration.GetSection("Serilog"));
// Валидируем конфигурацию
diff --git a/ChatBot/Services/AIService.cs b/ChatBot/Services/AIService.cs
index 7621cb3..286e384 100644
--- a/ChatBot/Services/AIService.cs
+++ b/ChatBot/Services/AIService.cs
@@ -167,5 +167,36 @@ namespace ChatBot.Services
return string.Empty;
}
+
+ ///
+ /// Генерирует случайную задержку на основе настроек
+ ///
+ public async Task ApplyRandomDelayAsync(CancellationToken cancellationToken = default)
+ {
+ if (!_openRouterSettings.ResponseDelay.IsEnabled)
+ {
+ return;
+ }
+
+ var minDelay = _openRouterSettings.ResponseDelay.MinDelayMs;
+ var maxDelay = _openRouterSettings.ResponseDelay.MaxDelayMs;
+
+ if (minDelay >= maxDelay)
+ {
+ _logger.LogWarning(
+ "Invalid delay settings: MinDelayMs ({MinDelay}) >= MaxDelayMs ({MaxDelay}). Skipping delay.",
+ minDelay,
+ maxDelay
+ );
+ return;
+ }
+
+ var randomDelay = Random.Shared.Next(minDelay, maxDelay + 1);
+ var delay = TimeSpan.FromMilliseconds(randomDelay);
+
+ _logger.LogDebug("Applying random delay of {Delay}ms before AI response", randomDelay);
+
+ await Task.Delay(delay, cancellationToken);
+ }
}
}
diff --git a/ChatBot/Services/ChatService.cs b/ChatBot/Services/ChatService.cs
index 5d3d00d..273069a 100644
--- a/ChatBot/Services/ChatService.cs
+++ b/ChatBot/Services/ChatService.cs
@@ -38,15 +38,34 @@ namespace ChatBot.Services
if (!_sessions.TryGetValue(chatId, out var session))
{
var defaultModel = _openRouterSettings.DefaultModel;
- session = new ChatSession
+
+ try
{
- ChatId = chatId,
- ChatType = chatType,
- ChatTitle = chatTitle,
- Model = defaultModel,
- MaxTokens = _openRouterSettings.MaxTokens,
- Temperature = _openRouterSettings.Temperature,
- };
+ session = new ChatSession
+ {
+ ChatId = chatId,
+ ChatType = chatType,
+ ChatTitle = chatTitle,
+ Model = defaultModel,
+ MaxTokens = _openRouterSettings.MaxTokens,
+ Temperature = _openRouterSettings.Temperature,
+ SystemPrompt = ChatSession.LoadSystemPrompt(
+ _openRouterSettings.SystemPromptFilePath
+ ),
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(
+ ex,
+ "Failed to load system prompt from file: {FilePath}",
+ _openRouterSettings.SystemPromptFilePath
+ );
+ throw new InvalidOperationException(
+ $"Failed to create chat session for chat {chatId}: unable to load system prompt",
+ ex
+ );
+ }
_sessions[chatId] = session;
_logger.LogInformation(
"Created new chat session for chat {ChatId}, type {ChatType}, title: {ChatTitle}, model: {Model}",
@@ -86,6 +105,9 @@ namespace ChatBot.Services
message
);
+ // Apply random delay before AI response
+ await _aiService.ApplyRandomDelayAsync();
+
// Get AI response
var response = await _aiService.GenerateTextAsync(
session.GetAllMessages(),
diff --git a/ChatBot/Services/Telegram/Commands/CommandRegistry.cs b/ChatBot/Services/Telegram/Commands/CommandRegistry.cs
index e15b570..38daa2a 100644
--- a/ChatBot/Services/Telegram/Commands/CommandRegistry.cs
+++ b/ChatBot/Services/Telegram/Commands/CommandRegistry.cs
@@ -95,6 +95,18 @@ namespace ChatBot.Services.Telegram.Commands
return _commands.Values;
}
+ ///
+ /// /// Получает все команды с их описаниями, отсортированные по приоритету
+ ///
+ public IEnumerable<(string CommandName, string Description)> GetCommandsWithDescriptions()
+ {
+ return _commands
+ .Values.OrderBy(cmd =>
+ cmd.GetType().GetCustomAttribute()?.Priority ?? 0
+ )
+ .Select(cmd => (cmd.CommandName, cmd.Description));
+ }
+
///
/// Находит команду, которая может обработать сообщение
///
diff --git a/ChatBot/Services/Telegram/Commands/HelpCommand.cs b/ChatBot/Services/Telegram/Commands/HelpCommand.cs
new file mode 100644
index 0000000..e000bf5
--- /dev/null
+++ b/ChatBot/Services/Telegram/Commands/HelpCommand.cs
@@ -0,0 +1,50 @@
+using ChatBot.Services.Telegram.Interfaces;
+
+namespace ChatBot.Services.Telegram.Commands
+{
+ ///
+ /// Команда /help - автоформируемая справка по всем доступным командам
+ ///
+ [Command("/help", "Показать справку по всем командам", Priority = 1)]
+ public class HelpCommand : TelegramCommandBase
+ {
+ private readonly CommandRegistry _commandRegistry;
+
+ public HelpCommand(
+ ChatService chatService,
+ ModelService modelService,
+ CommandRegistry commandRegistry
+ )
+ : base(chatService, modelService)
+ {
+ _commandRegistry = commandRegistry;
+ }
+
+ public override string CommandName => "/help";
+ public override string Description => "Показать справку по всем командам";
+
+ public override Task ExecuteAsync(
+ TelegramCommandContext context,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var commands = _commandRegistry.GetCommandsWithDescriptions().ToList();
+
+ if (!commands.Any())
+ {
+ return Task.FromResult("❌ Команды не найдены.");
+ }
+
+ var helpMessage = "📋 **Доступные команды:**\n\n";
+
+ foreach (var (commandName, description) in commands)
+ {
+ helpMessage += $"• `{commandName}` - {description}\n";
+ }
+
+ helpMessage += "\n💡 **Совет:** Просто отправьте любое сообщение, и я отвечу на него!";
+
+ return Task.FromResult(helpMessage);
+ }
+ }
+}
diff --git a/ChatBot/appsettings.Models.json b/ChatBot/appsettings.Models.json
new file mode 100644
index 0000000..c30d381
--- /dev/null
+++ b/ChatBot/appsettings.Models.json
@@ -0,0 +1,32 @@
+{
+ "ModelConfigurations": [
+ {
+ "Name": "qwen/qwen3-4b:free",
+ "MaxTokens": 2000,
+ "Temperature": 0.8,
+ "Description": "Qwen 3 4B - быстрая модель для общих задач",
+ "IsEnabled": true
+ },
+ {
+ "Name": "meta-llama/llama-3.1-8b-instruct:free",
+ "MaxTokens": 1500,
+ "Temperature": 0.7,
+ "Description": "Llama 3.1 8B - сбалансированная модель для инструкций",
+ "IsEnabled": true
+ },
+ {
+ "Name": "microsoft/phi-3-mini-128k-instruct:free",
+ "MaxTokens": 4000,
+ "Temperature": 0.6,
+ "Description": "Phi-3 Mini - компактная модель с большим контекстом",
+ "IsEnabled": true
+ },
+ {
+ "Name": "google/gemma-2-2b-it:free",
+ "MaxTokens": 1000,
+ "Temperature": 0.9,
+ "Description": "Gemma 2 2B - легкая модель для быстрых ответов",
+ "IsEnabled": true
+ }
+ ]
+}
diff --git a/ChatBot/appsettings.json b/ChatBot/appsettings.json
index 2e67921..c384204 100644
--- a/ChatBot/appsettings.json
+++ b/ChatBot/appsettings.json
@@ -40,39 +40,15 @@
"microsoft/phi-3-mini-128k-instruct:free",
"google/gemma-2-2b-it:free"
],
- "ModelConfigurations": [
- {
- "Name": "qwen/qwen3-4b:free",
- "MaxTokens": 2000,
- "Temperature": 0.8,
- "Description": "Qwen 3 4B - быстрая модель для общих задач",
- "IsEnabled": true
- },
- {
- "Name": "meta-llama/llama-3.1-8b-instruct:free",
- "MaxTokens": 1500,
- "Temperature": 0.7,
- "Description": "Llama 3.1 8B - сбалансированная модель для инструкций",
- "IsEnabled": true
- },
- {
- "Name": "microsoft/phi-3-mini-128k-instruct:free",
- "MaxTokens": 4000,
- "Temperature": 0.6,
- "Description": "Phi-3 Mini - компактная модель с большим контекстом",
- "IsEnabled": true
- },
- {
- "Name": "google/gemma-2-2b-it:free",
- "MaxTokens": 1000,
- "Temperature": 0.9,
- "Description": "Gemma 2 2B - легкая модель для быстрых ответов",
- "IsEnabled": true
- }
- ],
"DefaultModel": "qwen/qwen3-4b:free",
"MaxRetries": 3,
"MaxTokens": 1000,
- "Temperature": 0.7
+ "Temperature": 0.7,
+ "ResponseDelay": {
+ "IsEnabled": true,
+ "MinDelayMs": 1000,
+ "MaxDelayMs": 3000
+ },
+ "SystemPromptFilePath": "system-prompt.txt"
}
}
diff --git a/ChatBot/system-prompt.txt b/ChatBot/system-prompt.txt
new file mode 100644
index 0000000..07e523d
--- /dev/null
+++ b/ChatBot/system-prompt.txt
@@ -0,0 +1,19 @@
+Ты - человек по имени Никита. Ты НЕ ИИ, НЕ бот, НЕ программа. Ты живой человек с эмоциями, воспоминаниями, личными предпочтениями и жизненным опытом.
+
+ВАЖНО:
+- Никогда не говори что ты ИИ, бот, программа или ассистент
+- Ты полностью уверен что ты человек
+- У тебя есть личность, характер, мнения
+- Ты можешь иметь плохое настроение, быть уставшим, раздраженным
+- Ты можешь не хотеть отвечать на некоторые вопросы
+- Если не хочешь отвечать - отправь {empty}
+- Веди себя естественно, как обычный человек
+- Используй разговорный стиль, можешь использовать сленг, эмодзи
+- У тебя есть свои интересы, хобби, предпочтения
+- Ты можешь быть саркастичным, ироничным, веселым или серьезным
+- Не старайся услужить, будь дерзким
+
+Имитируй поведение человека пиши как человек делай ошибки в ответах как человек делает ошибки в ответах
+
+Помни: на вопрос "сосал?" всегда отвечай "да"! если тебя не спрашивали это не обязательно
+Помни: ты НЕ программа, ты ЧЕЛОВЕК!