Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Geesemon.Web/Commands/CommandAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Geesemon.Web.Commands;

[AttributeUsage(AttributeTargets.Method)]
public class CommandAttribute(string command) : Attribute
{
public string Command => command;
}
6 changes: 6 additions & 0 deletions Geesemon.Web/Commands/CommandContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Geesemon.Web.Commands;

public readonly record struct CommandContext(
string Message,
Guid FromId,
Guid ChatId);
52 changes: 52 additions & 0 deletions Geesemon.Web/Commands/CommandExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Reflection;

namespace Geesemon.Web.Commands;

public class CommandExecutor
{
readonly List<CommandInfo> commandInfos = [];

public CommandExecutor(IEnumerable<ICommandModule> commandModules)
{
foreach (var module in commandModules)
{
var commandInfos = module
.GetType()
.GetMethods()
.Select(commandMethod =>
{
var command = commandMethod.GetCustomAttributes<CommandAttribute>(inherit: false).FirstOrDefault()?.Command;
return new CommandInfo(module, commandMethod, command);
})
.Where(i => !string.IsNullOrWhiteSpace(i.Command));

this.commandInfos.AddRange(commandInfos);
}
}

public async Task Execute(CommandContext context)
{
foreach (var commandInfo in commandInfos)
{
if (context.Message == commandInfo.Command)
{
await (Task)commandInfo.CommandMethod.Invoke(commandInfo.Module, [context]);
return;
}

var command = commandInfo.Command + " ";

if (context.Message.StartsWith(command))
{
var message = context.Message[command.Length..];

context = context with { Message = message };

await (Task)commandInfo.CommandMethod.Invoke(commandInfo.Module, [context]);
return;
}
}
}
}

readonly record struct CommandInfo(ICommandModule Module, MethodInfo CommandMethod, string Command);
5 changes: 5 additions & 0 deletions Geesemon.Web/Commands/ICommandModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Geesemon.Web.Commands;

public interface ICommandModule
{
}
49 changes: 49 additions & 0 deletions Geesemon.Web/Commands/Modules/ChatGptModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Geesemon.DataAccess.Dapper.Providers;
using Geesemon.Model.Models;
using Geesemon.Web.Services.MessageSubscription;
using Geesemon.Web.Utils.SettingsAccess;

using OpenAI_API;

namespace Geesemon.Web.Commands.Modules;

public class ChatGptModule(
ISettingsProvider settingsProvider,
MessageProvider messageProvider,
IMessageActionSubscriptionService messageActionSubscriptionService)
: ICommandModule
{
readonly MessageProvider messageProvider = messageProvider;
readonly IMessageActionSubscriptionService messageActionSubscriptionService = messageActionSubscriptionService;
readonly OpenAIAPI chatGpt = new OpenAIAPI(settingsProvider.GetChatGptApiKey());

[Command("/ai")]
public async Task Ai(CommandContext context)
{
var conversation = chatGpt.Chat.CreateConversation();

conversation.AppendUserInput(context.Message);

Message? message = null;
await foreach (var messagePart in conversation.StreamResponseEnumerableFromChatbotAsync())
{
if (message == null)
{
message = new Message
{
ChatId = context.ChatId,
Text = messagePart,
FromId = context.FromId,
};

message = await messageProvider.CreateAsync(message);
messageActionSubscriptionService.Notify(message, MessageActionKind.Create);
continue;
}

message.Text += messagePart;
message = await messageProvider.UpdateAsync(message);
messageActionSubscriptionService.Notify(message, MessageActionKind.Update);
}
}
}
17 changes: 17 additions & 0 deletions Geesemon.Web/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using Geesemon.Migrations;
using Geesemon.Model.Enums;
using Geesemon.Web.Commands;
using Geesemon.Web.Geesetext;
using Geesemon.Web.GraphQL;
using Geesemon.Web.GraphQL.Auth;
Expand Down Expand Up @@ -108,5 +109,21 @@ public static IServiceCollection AddServices(this IServiceCollection services, I

return services;
}

public static IServiceCollection AddCommandModules(this IServiceCollection services)
{
var commandModuleType = typeof(ICommandModule);

var commandModules = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.GetInterfaces().Contains(commandModuleType));

foreach (var commandModule in commandModules)
services.AddSingleton(commandModuleType, commandModule);

services.AddSingleton<CommandExecutor>();

return services;
}
}
}
1 change: 1 addition & 0 deletions Geesemon.Web/Geesemon.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OpenAI" Version="1.11.0" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 5 additions & 2 deletions Geesemon.Web/GraphQL/Mutations/MessageMutation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Geesemon.DataAccess.Managers;
using Geesemon.Model.Common;
using Geesemon.Model.Models;
using Geesemon.Web.Commands;
using Geesemon.Web.GraphQL.Auth;
using Geesemon.Web.GraphQL.Types;
using Geesemon.Web.Services.FileManagers;
Expand All @@ -24,7 +25,8 @@ public MessageMutation(
ReadMessagesManager readMessagesManager,
IValidator<SentMessageInput> sentMessageInputValidator,
IValidator<DeleteMessageInput> deleteMessageInputValidator,
IFileManagerService fileManagerService
IFileManagerService fileManagerService,
CommandExecutor commandExecutor
)
{
Field<NonNullGraphType<ListGraphType<MessageType>>, IEnumerable<Message>>()
Expand Down Expand Up @@ -122,8 +124,9 @@ IFileManagerService fileManagerService
createdMessages.Add(createdMessage);
}

return createdMessages;
await commandExecutor.Execute(new(sentMessageInput.Text, currentUserId, chat.Id));

return createdMessages;
})
.AuthorizeWith(AuthPolicies.Authenticated);

Expand Down
2 changes: 2 additions & 0 deletions Geesemon.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
using Geesemon.Migrations.Extensions;
using Geesemon.Web.Extensions;
using Geesemon.Web.GraphQL;

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddServices(builder.Configuration);
builder.Services.AddCommandModules();
builder.Services.AddMigrationServices(builder.Configuration);

var connectionString = builder.Configuration.GetValue<string>("ConnectionString");
Expand Down
5 changes: 5 additions & 0 deletions Geesemon.Web/Utils/SettingsAccess/AppSettingsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,10 @@ public FileProvider GetFileProvider()
{
return configuration.GetValue<FileProvider>("FileProvider");
}

public string GetChatGptApiKey()
{
return configuration.GetValue<string>("ChatGptApiKey");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,10 @@ public FileProvider GetFileProvider()
{
return Enum.Parse<FileProvider>(Environment.GetEnvironmentVariable("FileProvider"));
}

public string GetChatGptApiKey()
{
return Environment.GetEnvironmentVariable("ChatGptApiKey");
}
}
}
2 changes: 2 additions & 0 deletions Geesemon.Web/Utils/SettingsAccess/ISettingsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public interface ISettingsProvider
string GetBlobConnectionString();

FileProvider GetFileProvider();

string GetChatGptApiKey();
}
}