diff --git a/Bot/Handler/ChatCommand/ChatHandler.cs b/Bot/Handler/ChatCommand/ChatHandler.cs new file mode 100644 index 0000000..d9c6c1f --- /dev/null +++ b/Bot/Handler/ChatCommand/ChatHandler.cs @@ -0,0 +1,62 @@ +using System.Text; +using Discord.WebSocket; +using MediatR; +using Microsoft.Extensions.Options; +using OllamaSharp; + +namespace Lunaris2.Handler.ChatCommand +{ + public record ChatCommand(SocketMessage Message, string FilteredMessage) : IRequest; + + public class ChatHandler : IRequestHandler + { + private readonly OllamaApiClient _ollama; + private readonly Dictionary _chatContexts = new(); + + public ChatHandler(IOptions chatSettings) + { + var uri = new Uri(chatSettings.Value.Url); + + _ollama = new OllamaApiClient(uri) + { + SelectedModel = chatSettings.Value.Model + }; + } + + public async Task Handle(ChatCommand command, CancellationToken cancellationToken) + { + var channelId = command.Message.Channel.Id; + _chatContexts.TryAdd(channelId, null); + + var userMessage = command.FilteredMessage; + + using var setTyping = command.Message.Channel.EnterTypingState(); + + if (string.IsNullOrWhiteSpace(userMessage)) + { + await command.Message.Channel.SendMessageAsync("Am I expected to read your mind?"); + setTyping.Dispose(); + return; + } + + var response = await GenerateResponse(userMessage, channelId, cancellationToken); + await command.Message.Channel.SendMessageAsync(response); + + setTyping.Dispose(); + } + + private async Task GenerateResponse(string userMessage, ulong channelId, CancellationToken cancellationToken) + { + var response = new StringBuilder(); + + if (_chatContexts[channelId] == null) + { + _chatContexts[channelId] = _ollama.Chat(stream => response.Append(stream.Message?.Content ?? "")); + } + + await _chatContexts[channelId].Send(userMessage, cancellationToken); + + return response.ToString(); + } + } +} diff --git a/Bot/Handler/ChatCommand/ChatSettings.cs b/Bot/Handler/ChatCommand/ChatSettings.cs new file mode 100644 index 0000000..48697fc --- /dev/null +++ b/Bot/Handler/ChatCommand/ChatSettings.cs @@ -0,0 +1,7 @@ +namespace Lunaris2.Handler.ChatCommand; + +public class ChatSettings +{ + public string Url { get; set; } + public string Model { get; set; } +} \ No newline at end of file diff --git a/Bot/Handler/MessageReceivedHandler.cs b/Bot/Handler/MessageReceivedHandler.cs index b1e744f..65d36ee 100644 --- a/Bot/Handler/MessageReceivedHandler.cs +++ b/Bot/Handler/MessageReceivedHandler.cs @@ -1,34 +1,37 @@ -using Lunaris2.Handler.GoodByeCommand; -using Lunaris2.Handler.MusicPlayer.JoinCommand; -using Lunaris2.Handler.MusicPlayer.PlayCommand; -using Lunaris2.Handler.MusicPlayer.SkipCommand; +using System.Text.RegularExpressions; +using Discord.WebSocket; using Lunaris2.Notification; -using Lunaris2.SlashCommand; using MediatR; namespace Lunaris2.Handler; -public class MessageReceivedHandler(ISender mediator) : INotificationHandler +public class MessageReceivedHandler : INotificationHandler { + private readonly DiscordSocketClient _client; + private readonly ISender _mediatir; + + public MessageReceivedHandler(DiscordSocketClient client, ISender mediatir) + { + _client = client; + _mediatir = mediatir; + } + public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken) { - switch (notification.Message.CommandName) + await BotMentioned(notification, cancellationToken); + } + + private async Task BotMentioned(MessageReceivedNotification notification, CancellationToken cancellationToken) + { + if (notification.Message.MentionedUsers.Any(user => user.Id == _client.CurrentUser.Id)) { - case Command.Hello.Name: - await mediator.Send(new HelloCommand.HelloCommand(notification.Message), cancellationToken); - break; - case Command.Goodbye.Name: - await mediator.Send(new GoodbyeCommand(notification.Message), cancellationToken); - break; - case Command.Join.Name: - await mediator.Send(new JoinCommand(notification.Message), cancellationToken); - break; - case Command.Play.Name: - await mediator.Send(new PlayCommand(notification.Message), cancellationToken); - break; - case Command.Skip.Name: - await mediator.Send(new SkipCommand(notification.Message), cancellationToken); - break; + // The bot was mentioned + const string pattern = "<.*?>"; + const string replacement = ""; + var regex = new Regex(pattern); + var messageContent = regex.Replace(notification.Message.Content, replacement); + + await _mediatir.Send(new ChatCommand.ChatCommand(notification.Message, messageContent), cancellationToken); } } } \ No newline at end of file diff --git a/Bot/Handler/SlashCommandReceivedHandler.cs b/Bot/Handler/SlashCommandReceivedHandler.cs new file mode 100644 index 0000000..3589173 --- /dev/null +++ b/Bot/Handler/SlashCommandReceivedHandler.cs @@ -0,0 +1,34 @@ +using Lunaris2.Handler.GoodByeCommand; +using Lunaris2.Handler.MusicPlayer.JoinCommand; +using Lunaris2.Handler.MusicPlayer.PlayCommand; +using Lunaris2.Handler.MusicPlayer.SkipCommand; +using Lunaris2.Notification; +using Lunaris2.SlashCommand; +using MediatR; + +namespace Lunaris2.Handler; + +public class SlashCommandReceivedHandler(ISender mediator) : INotificationHandler +{ + public async Task Handle(SlashCommandReceivedNotification notification, CancellationToken cancellationToken) + { + switch (notification.Message.CommandName) + { + case Command.Hello.Name: + await mediator.Send(new HelloCommand.HelloCommand(notification.Message), cancellationToken); + break; + case Command.Goodbye.Name: + await mediator.Send(new GoodbyeCommand(notification.Message), cancellationToken); + break; + case Command.Join.Name: + await mediator.Send(new JoinCommand(notification.Message), cancellationToken); + break; + case Command.Play.Name: + await mediator.Send(new PlayCommand(notification.Message), cancellationToken); + break; + case Command.Skip.Name: + await mediator.Send(new SkipCommand(notification.Message), cancellationToken); + break; + } + } +} \ No newline at end of file diff --git a/Bot/Lunaris2.csproj b/Bot/Lunaris2.csproj index 85d4d02..3e2682d 100644 --- a/Bot/Lunaris2.csproj +++ b/Bot/Lunaris2.csproj @@ -19,6 +19,7 @@ + diff --git a/Bot/Notification/DiscordEventListener.cs b/Bot/Notification/DiscordEventListener.cs index 6c4bcde..2d80b42 100644 --- a/Bot/Notification/DiscordEventListener.cs +++ b/Bot/Notification/DiscordEventListener.cs @@ -19,13 +19,19 @@ public class DiscordEventListener(DiscordSocketClient client, IServiceScopeFacto public async Task StartAsync() { - client.SlashCommandExecuted += OnMessageReceivedAsync; + client.SlashCommandExecuted += OnSlashCommandRecievedAsync; + client.MessageReceived += OnMessageReceivedAsync; await Task.CompletedTask; } - private async Task OnMessageReceivedAsync(SocketSlashCommand arg) + private async Task OnMessageReceivedAsync(SocketMessage arg) { await Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken); } + + private async Task OnSlashCommandRecievedAsync(SocketSlashCommand arg) + { + await Mediator.Publish(new SlashCommandReceivedNotification(arg), _cancellationToken); + } } \ No newline at end of file diff --git a/Bot/Notification/MessageReceivedNotification.cs b/Bot/Notification/MessageReceivedNotification.cs index 63802bf..2008a17 100644 --- a/Bot/Notification/MessageReceivedNotification.cs +++ b/Bot/Notification/MessageReceivedNotification.cs @@ -3,7 +3,7 @@ using MediatR; namespace Lunaris2.Notification; -public class MessageReceivedNotification(SocketSlashCommand message) : INotification +public class MessageReceivedNotification(SocketMessage message) : INotification { - public SocketSlashCommand Message { get; } = message ?? throw new ArgumentNullException(nameof(message)); + public SocketMessage Message { get; } = message ?? throw new ArgumentNullException(nameof(message)); } \ No newline at end of file diff --git a/Bot/Notification/SlashCommandReceivedNotification.cs b/Bot/Notification/SlashCommandReceivedNotification.cs new file mode 100644 index 0000000..e0358da --- /dev/null +++ b/Bot/Notification/SlashCommandReceivedNotification.cs @@ -0,0 +1,9 @@ +using Discord.WebSocket; +using MediatR; + +namespace Lunaris2.Notification; + +public class SlashCommandReceivedNotification(SocketSlashCommand message) : INotification +{ + public SocketSlashCommand Message { get; } = message ?? throw new ArgumentNullException(nameof(message)); +} \ No newline at end of file diff --git a/Bot/Program.cs b/Bot/Program.cs index ffd49a7..5f68df1 100644 --- a/Bot/Program.cs +++ b/Bot/Program.cs @@ -3,6 +3,7 @@ using Discord; using Discord.Commands; using Discord.Interactions; using Discord.WebSocket; +using Lunaris2.Handler.ChatCommand; using Lunaris2.Handler.MusicPlayer; using Lunaris2.Notification; using Lunaris2.SlashCommand; @@ -54,7 +55,9 @@ public class Program nodeConfiguration.Authorization = configuration["LavaLinkPassword"]; }) .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton() + .Configure(configuration.GetSection("LLM")); client.Ready += () => Client_Ready(client); client.Log += Log; diff --git a/Bot/SlashCommand/SlashCommandRegistration.cs b/Bot/SlashCommand/SlashCommandRegistration.cs index 26331e9..11b5f58 100644 --- a/Bot/SlashCommand/SlashCommandRegistration.cs +++ b/Bot/SlashCommand/SlashCommandRegistration.cs @@ -12,7 +12,7 @@ public static class SlashCommandRegistration RegisterCommand(client, Command.Join.Name, Command.Join.Description); RegisterCommand(client, Command.Skip.Name, Command.Skip.Description); RegisterCommand(client, Command.Play.Name, Command.Play.Description, Command.Play.Options); - RegisterCommand(client, Command.Stop.Name, Command.Stop.Description); + RegisterCommand(client, Command.Stop.Name, Command.Stop.Description); } private static void RegisterCommand( diff --git a/Bot/appsettings.json b/Bot/appsettings.json index 9901e00..9c46724 100644 --- a/Bot/appsettings.json +++ b/Bot/appsettings.json @@ -10,4 +10,8 @@ "LavaLinkPassword": "youshallnotpass", "LavaLinkHostname": "127.0.0.1", "LavaLinkPort": 2333 + "LLM": { + "Url": "http://192.168.50.54:11434", + "Model": "gemma" + } }