Compare commits

...

2 Commits

Author SHA1 Message Date
a1d20fd732 Add chat functionality (#1)
* Working chatbot

* Clean

* Working LLM chatbot

---------

Co-authored-by: Myx <info@azaaxin.com>
2024-06-01 23:22:47 +02:00
3d7655a902 Update readme.md 2024-04-15 00:00:38 +02:00
12 changed files with 160 additions and 29 deletions

View File

@@ -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<ChatCommand>
{
private readonly OllamaApiClient _ollama;
private readonly Dictionary<ulong, Chat?> _chatContexts = new();
public ChatHandler(IOptions<ChatSettings> 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<string> 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();
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Lunaris2.Handler.ChatCommand;
public class ChatSettings
{
public string Url { get; set; }
public string Model { get; set; }
}

View File

@@ -1,34 +1,37 @@
using Lunaris2.Handler.GoodByeCommand; using System.Text.RegularExpressions;
using Lunaris2.Handler.MusicPlayer.JoinCommand; using Discord.WebSocket;
using Lunaris2.Handler.MusicPlayer.PlayCommand;
using Lunaris2.Handler.MusicPlayer.SkipCommand;
using Lunaris2.Notification; using Lunaris2.Notification;
using Lunaris2.SlashCommand;
using MediatR; using MediatR;
namespace Lunaris2.Handler; namespace Lunaris2.Handler;
public class MessageReceivedHandler(ISender mediator) : INotificationHandler<MessageReceivedNotification> public class MessageReceivedHandler : INotificationHandler<MessageReceivedNotification>
{ {
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) 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: // The bot was mentioned
await mediator.Send(new HelloCommand.HelloCommand(notification.Message), cancellationToken); const string pattern = "<.*?>";
break; const string replacement = "";
case Command.Goodbye.Name: var regex = new Regex(pattern);
await mediator.Send(new GoodbyeCommand(notification.Message), cancellationToken); var messageContent = regex.Replace(notification.Message.Content, replacement);
break;
case Command.Join.Name: await _mediatir.Send(new ChatCommand.ChatCommand(notification.Message, messageContent), cancellationToken);
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;
} }
} }
} }

View File

@@ -8,6 +8,8 @@ flowchart TD
PlayTrack --> NowPlayingEmbed PlayTrack --> NowPlayingEmbed
``` ```
## Steps in the code
| Name | Description | | Name | Description |
|--|--| |--|--|
| PlayHandler | Holds the logic for playing songs | | PlayHandler | Holds the logic for playing songs |
@@ -19,7 +21,7 @@ flowchart TD
There is also OnTrackEnd, when it get called an attempt is made to play the next song in queue. There is also OnTrackEnd, when it get called an attempt is made to play the next song in queue.
Short explaination for some of the variables used: ## Short explaination for some of the variables used:
| Variable | Type | Description | | Variable | Type | Description |
| --- | --- | --- | | --- | --- | --- |

View File

@@ -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<SlashCommandReceivedNotification>
{
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;
}
}
}

View File

@@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="OllamaSharp" Version="1.1.10" />
<PackageReference Include="Victoria" Version="6.0.23.324" /> <PackageReference Include="Victoria" Version="6.0.23.324" />
</ItemGroup> </ItemGroup>

View File

@@ -19,13 +19,19 @@ public class DiscordEventListener(DiscordSocketClient client, IServiceScopeFacto
public async Task StartAsync() public async Task StartAsync()
{ {
client.SlashCommandExecuted += OnMessageReceivedAsync; client.SlashCommandExecuted += OnSlashCommandRecievedAsync;
client.MessageReceived += OnMessageReceivedAsync;
await Task.CompletedTask; await Task.CompletedTask;
} }
private async Task OnMessageReceivedAsync(SocketSlashCommand arg) private async Task OnMessageReceivedAsync(SocketMessage arg)
{ {
await Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken); await Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken);
} }
private async Task OnSlashCommandRecievedAsync(SocketSlashCommand arg)
{
await Mediator.Publish(new SlashCommandReceivedNotification(arg), _cancellationToken);
}
} }

View File

@@ -3,7 +3,7 @@ using MediatR;
namespace Lunaris2.Notification; 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));
} }

View File

@@ -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));
}

View File

@@ -3,6 +3,7 @@ using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.Interactions; using Discord.Interactions;
using Discord.WebSocket; using Discord.WebSocket;
using Lunaris2.Handler.ChatCommand;
using Lunaris2.Handler.MusicPlayer; using Lunaris2.Handler.MusicPlayer;
using Lunaris2.Notification; using Lunaris2.Notification;
using Lunaris2.SlashCommand; using Lunaris2.SlashCommand;
@@ -54,7 +55,9 @@ public class Program
nodeConfiguration.Authorization = configuration["LavaLinkPassword"]; nodeConfiguration.Authorization = configuration["LavaLinkPassword"];
}) })
.AddSingleton<LavaNode>() .AddSingleton<LavaNode>()
.AddSingleton<MusicEmbed>(); .AddSingleton<MusicEmbed>()
.AddSingleton<ChatSettings>()
.Configure<ChatSettings>(configuration.GetSection("LLM"));
client.Ready += () => Client_Ready(client); client.Ready += () => Client_Ready(client);
client.Log += Log; client.Log += Log;

View File

@@ -12,7 +12,7 @@ public static class SlashCommandRegistration
RegisterCommand(client, Command.Join.Name, Command.Join.Description); RegisterCommand(client, Command.Join.Name, Command.Join.Description);
RegisterCommand(client, Command.Skip.Name, Command.Skip.Description); RegisterCommand(client, Command.Skip.Name, Command.Skip.Description);
RegisterCommand(client, Command.Play.Name, Command.Play.Description, Command.Play.Options); 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( private static void RegisterCommand(

View File

@@ -10,4 +10,8 @@
"LavaLinkPassword": "youshallnotpass", "LavaLinkPassword": "youshallnotpass",
"LavaLinkHostname": "127.0.0.1", "LavaLinkHostname": "127.0.0.1",
"LavaLinkPort": 2333 "LavaLinkPort": 2333
"LLM": {
"Url": "http://192.168.50.54:11434",
"Model": "gemma"
}
} }