Compare commits

...

4 Commits

Author SHA1 Message Date
54c5c68ba6 Update README.md 2024-06-01 23:35:40 +02:00
e16ff9cfaf Update README.md 2024-06-01 23:35:16 +02:00
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
13 changed files with 180 additions and 31 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 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<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)
{
switch (notification.Message.CommandName)
await BotMentioned(notification, cancellationToken);
}
private async Task BotMentioned(MessageReceivedNotification notification, CancellationToken cancellationToken)
{
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;
if (notification.Message.MentionedUsers.Any(user => user.Id == _client.CurrentUser.Id))
{
// 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);
}
}
}

View File

@@ -8,6 +8,8 @@ flowchart TD
PlayTrack --> NowPlayingEmbed
```
## Steps in the code
| Name | Description |
|--|--|
| 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.
Short explaination for some of the variables used:
## Short explaination for some of the variables used:
| 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.DependencyInjection" 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" />
</ItemGroup>

View File

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

View File

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

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.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<LavaNode>()
.AddSingleton<MusicEmbed>();
.AddSingleton<MusicEmbed>()
.AddSingleton<ChatSettings>()
.Configure<ChatSettings>(configuration.GetSection("LLM"));
client.Ready += () => Client_Ready(client);
client.Log += Log;

View File

@@ -2,9 +2,16 @@
```mermaid
flowchart TD
Program[Program] -->|Register| EventListener
EventListener[DiscordEventListener] --> A
EventListener[DiscordEventListener] --> A[MessageReceivedHandler]
A[MessageReceivedHandler] -->|Message| C{Send to correct command by
EventListener[DiscordEventListener] --> A2[SlashCommandReceivedHandler]
A --> |Message| f{If bot is mentioned}
f --> v[ChatHandler]
v --> o[Ollama Server]
o --> v
A2[SlashCommandReceivedHandler] -->|Message| C{Send to correct command by
looking at commandName}
C -->|JoinCommand| D[JoinHandler]
@@ -13,6 +20,17 @@ flowchart TD
C -->|GoodbyeCommand| G[GoodbyeHandler]
```
## Handler integrations
```mermaid
flowchart TD
D[JoinHandler] --> Disc[Discord Api]
E[PlayHandler] --> Disc[Discord Api]
F[HelloHandler] --> Disc[Discord Api]
G[GoodbyeHandler] --> Disc[Discord Api]
v[ChatHandler] --> Disc[Discord Api]
E --> Lava[Lavalink]
```
Program registers an event listener ```DiscordEventListener``` which publish a message :
```c#

View File

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