diff --git a/Examples/Examples/Chat/ChatWithImageGenExample.cs b/Examples/Examples/Chat/ChatWithImageGenExample.cs index 69f53a93..532998ed 100644 --- a/Examples/Examples/Chat/ChatWithImageGenExample.cs +++ b/Examples/Examples/Chat/ChatWithImageGenExample.cs @@ -1,8 +1,5 @@ using Examples.Utils; using MaIN.Core.Hub; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; namespace Examples; diff --git a/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs b/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs index 63c4c61f..ca710799 100644 --- a/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs +++ b/Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs @@ -1,8 +1,5 @@ using Examples.Utils; using MaIN.Core.Hub; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; namespace Examples; diff --git a/Examples/Examples/Examples.csproj b/Examples/Examples/Examples.csproj index 95fa62ce..3af31f3e 100644 --- a/Examples/Examples/Examples.csproj +++ b/Examples/Examples/Examples.csproj @@ -38,9 +38,4 @@ Always - - - - - diff --git a/Examples/Examples/Program.cs b/Examples/Examples/Program.cs index 2898c80f..d7ab4ec8 100644 --- a/Examples/Examples/Program.cs +++ b/Examples/Examples/Program.cs @@ -2,7 +2,6 @@ using Examples.Agents; using Examples.Agents.Flows; using MaIN.Core; -using MaIN.Domain.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -29,11 +28,7 @@ var services = new ServiceCollection(); services.AddSingleton(configuration); -services.AddMaIN(configuration, (options) => -{ - options.BackendType = BackendType.OpenAi; - options.OpenAiKey = ""; -}); +services.AddMaIN(configuration); RegisterExamples(services); diff --git a/Examples/Examples/Utils/ConsoleRenderer.cs b/Examples/Examples/Utils/ConsoleRenderer.cs deleted file mode 100644 index 61cfedc0..00000000 --- a/Examples/Examples/Utils/ConsoleRenderer.cs +++ /dev/null @@ -1,65 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace Examples.Utils; - -class ConsoleRenderer -{ - const double FONT_RATIO = 0.5; // Adjust based on your terminal font (0.5 for square pixels) - const bool USE_DITHERING = true; - - public static void DisplayImage(byte[] imageData, int maxWidth) - { - Console.OutputEncoding = System.Text.Encoding.UTF8; - - using var image = Image.Load(imageData); - - // High-quality resizing with Lanczos3 - var options = new ResizeOptions { - Size = CalculateSize(image.Width, image.Height, maxWidth), - Sampler = KnownResamplers.Lanczos3, - Compand = true - }; - - image.Mutate(x => { - x.Resize(options); - if(USE_DITHERING) x.Dither(KnownDitherings.FloydSteinberg); - }); - - RenderWithHalfBlocks(image); - } - - static Size CalculateSize(int origWidth, int origHeight, int maxWidth) - { - double ratio = (double)origWidth / origHeight * FONT_RATIO; - int width = Math.Min(maxWidth, Console.WindowWidth); - int height = (int)(width / ratio); - return new Size(width, height); - } - - static void RenderWithHalfBlocks(Image image) - { - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height - 1; y += 2) - { - var topRow = accessor.GetRowSpan(y); - var bottomRow = accessor.GetRowSpan(y + 1); - - for (int x = 0; x < topRow.Length; x++) - { - var top = topRow[x]; - var bottom = bottomRow[x]; - - Console.Write( - $"\x1b[38;2;{top.R};{top.G};{top.B}m" + // Foreground (top) - $"\x1b[48;2;{bottom.R};{bottom.G};{bottom.B}m" + // Background (bottom) - "▄" + // Lower half block - "\x1b[0m"); // Reset - } - Console.WriteLine(); - } - }); - } -} \ No newline at end of file diff --git a/Examples/Examples/Utils/ImagePreviewer.cs b/Examples/Examples/Utils/ImagePreviewer.cs index eecb5d4c..e63ab1cf 100644 --- a/Examples/Examples/Utils/ImagePreviewer.cs +++ b/Examples/Examples/Utils/ImagePreviewer.cs @@ -9,7 +9,7 @@ namespace Examples.Utils; public static class ImagePreview { - public static void ShowImage(byte[] imageData, string extension = "png") + public static void ShowImage(byte[]? imageData, string extension = "png") { // Validate extension if (string.IsNullOrWhiteSpace(extension) || extension.Contains(".")) @@ -21,7 +21,7 @@ public static void ShowImage(byte[] imageData, string extension = "png") $"{Guid.NewGuid()}.{extension}" ); - File.WriteAllBytes(tempFile, imageData); + File.WriteAllBytes(tempFile, imageData!); try { diff --git a/MaIN.Server/Program.cs b/MaIN.Server/Program.cs index b684cb24..6d75ac5b 100644 --- a/MaIN.Server/Program.cs +++ b/MaIN.Server/Program.cs @@ -1,5 +1,6 @@ using MaIN.Domain.Entities; using MaIN.Services; +using MaIN.Services.Services.Abstract; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; @@ -45,7 +46,7 @@ return Results.Ok(models); }); -app.MapDelete("/api/llm/session/{chatId}", async ([FromServices] ILLMService llmService, string chatId) => +app.MapDelete("/api/llm/session/{chatId}", async ([FromServices] ILLMService llmService, string? chatId) => { await llmService.CleanSessionCache(chatId); return Results.Ok($"Session chat {chatId} has been cleared."); @@ -66,6 +67,6 @@ app.Run(); // Request DTOs -record ChatRequest(Chat? Chat, bool InteractiveUpdates = false, bool NewSession = false); +record ChatRequest(Chat Chat, bool InteractiveUpdates = false, bool NewSession = false); -record AskMemoryRequest(Chat? Chat, Dictionary? TextData = null, Dictionary? FileData = null, List? Memory = null); \ No newline at end of file +record AskMemoryRequest(Chat Chat, Dictionary? TextData = null, Dictionary? FileData = null, List? Memory = null); \ No newline at end of file diff --git a/MaIN.sln b/MaIN.sln index 81cc07de..56833d1f 100644 --- a/MaIN.sln +++ b/MaIN.sln @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaIN.Core.UnitTests", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.SimpleConsole", "Examples\Examples.SimpleConsole\Examples.SimpleConsole.csproj", "{75DEBB8A-75CD-44BA-9369-3916950428EF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaIN.InferPage", "src\MaIN.InferPage\MaIN.InferPage.csproj", "{B691188A-1170-489D-8729-A13108C12C57}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +72,10 @@ Global {75DEBB8A-75CD-44BA-9369-3916950428EF}.Debug|Any CPU.Build.0 = Debug|Any CPU {75DEBB8A-75CD-44BA-9369-3916950428EF}.Release|Any CPU.ActiveCfg = Release|Any CPU {75DEBB8A-75CD-44BA-9369-3916950428EF}.Release|Any CPU.Build.0 = Release|Any CPU + {B691188A-1170-489D-8729-A13108C12C57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B691188A-1170-489D-8729-A13108C12C57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B691188A-1170-489D-8729-A13108C12C57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B691188A-1170-489D-8729-A13108C12C57}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {781BDD20-65BA-4C5D-815B-D8A15931570A} = {28851935-517F-438D-BF7C-02FEB1A37A68} diff --git a/Releases/0.0.8-pre.md b/Releases/0.0.8-pre.md new file mode 100644 index 00000000..94b00451 --- /dev/null +++ b/Releases/0.0.8-pre.md @@ -0,0 +1,16 @@ +# 0.0.8 pre-release + +- Project cleanup +- New version of CLI (introduced infer and config commands) +- name of model **breaking change** + +In order to align with new breaking change, align your already downloaded models to this names (and ofc .gguf extension) +-- DeepSeekR1-8b +-- Fox-1.6b +-- gemma2-2b +-- Llama3.1-8b +-- Llama3.2-3b +-- Llava +-- Nomic +-- phi3.5 +-- Qwen2.5 \ No newline at end of file diff --git a/src/MaIN.Core.UnitTests/AgentContextTests.cs b/src/MaIN.Core.UnitTests/AgentContextTests.cs new file mode 100644 index 00000000..c3398630 --- /dev/null +++ b/src/MaIN.Core.UnitTests/AgentContextTests.cs @@ -0,0 +1,214 @@ +using MaIN.Core.Hub.Contexts; +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Agents; +using MaIN.Services.Models; +using MaIN.Services.Services.Abstract; +using Moq; + +namespace MaIN.Core.UnitTests; + +public class AgentContextTests +{ + private readonly Mock _mockAgentService; + private readonly AgentContext _agentContext; + + public AgentContextTests() + { + _mockAgentService = new Mock(); + _agentContext = new AgentContext(_mockAgentService.Object); + } + + [Fact] + public void Constructor_ShouldInitializeNewAgent() + { + // Assert + var agentId = _agentContext.GetAgentId(); + var agent = _agentContext.GetAgent(); + + Assert.NotNull(agentId); + Assert.NotEmpty(agentId); + Assert.NotNull(agent); + Assert.NotNull(agent.Context); + Assert.NotNull(agent.Behaviours); + Assert.Equal("Agent created by MaIN", agent.Description); + } + + [Fact] + public void WithId_ShouldSetAgentId() + { + // Arrange + var expectedId = "test-agent-id"; + + // Act + var result = _agentContext.WithId(expectedId); + + // Assert + Assert.Equal(expectedId, _agentContext.GetAgentId()); + Assert.Equal(result, _agentContext); + } + + [Fact] + public void WithName_ShouldSetAgentName() + { + // Arrange + var expectedName = "Test Agent"; + + // Act + var result = _agentContext.WithName(expectedName); + + // Assert + Assert.Equal(expectedName, _agentContext.GetAgent().Name); + Assert.Equal(result, _agentContext); + } + + [Fact] + public void WithModel_ShouldSetAgentModel() + { + // Arrange + var expectedModel = "gpt-4"; + + // Act + var result = _agentContext.WithModel(expectedModel); + + // Assert + Assert.Equal(expectedModel, _agentContext.GetAgent().Model); + Assert.Equal(result, _agentContext); + } + + [Fact] + public void WithInitialPrompt_ShouldSetInstruction() + { + // Arrange + var expectedPrompt = "You are a helpful assistant"; + + // Act + var result = _agentContext.WithInitialPrompt(expectedPrompt); + + // Assert + Assert.Equal(expectedPrompt, _agentContext.GetAgent().Context.Instruction); + Assert.Equal(result, _agentContext); + } + + [Fact] + public void WithSteps_ShouldSetAgentSteps() + { + // Arrange + var expectedSteps = new List { "ANALYZE", "RESPOND" }; + + // Act + var result = _agentContext.WithSteps(expectedSteps); + + // Assert + Assert.Equal(expectedSteps, _agentContext.GetAgent().Context.Steps); + Assert.Equal(result, _agentContext); + } + + [Fact] + public void WithBehaviour_ShouldAddBehaviourAndSetCurrent() + { + // Arrange + var behaviourName = "Friendly"; + var behaviourInstruction = "Be helpful and friendly"; + + // Act + var result = _agentContext.WithBehaviour(behaviourName, behaviourInstruction); + + // Assert + var agent = _agentContext.GetAgent(); + Assert.Contains(behaviourName, agent.Behaviours!.Keys); + Assert.Equal(behaviourInstruction, agent.Behaviours[behaviourName]); + Assert.Equal(behaviourName, agent.CurrentBehaviour); + Assert.Equal(result, _agentContext); + } + + [Fact] + public async Task CreateAsync_ShouldCallAgentServiceCreateAgent() + { + // Arrange + var agent = new Agent() {Id = Guid.NewGuid().ToString(), CurrentBehaviour = "Default", Context = new AgentData()}; + _mockAgentService + .Setup(s => s.CreateAgent( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(agent); + + // Act + var result = await _agentContext.CreateAsync(true, false); + + // Assert + _mockAgentService.Verify( + s => s.CreateAgent( + It.IsAny(), + It.Is(f => f == true), + It.Is(r => r == false), + It.IsAny()), + Times.Once); + Assert.Equal(_agentContext, result); + } + + [Fact] + public async Task ProcessAsync_WithStringMessage_ShouldReturnChatResult() + { + // Arrange + var message = "Hello, agent!"; + var chat = new Chat { Id = _agentContext.GetAgentId(), Messages = new List() }; + var chatResult = new ChatResult { Done = true, Model = "test-model", Message = new MessageDto()}; + + _mockAgentService + .Setup(s => s.GetChatByAgent(_agentContext.GetAgentId())) + .ReturnsAsync(chat); + + _mockAgentService + .Setup(s => s.Process(It.IsAny(), _agentContext.GetAgentId(), It.IsAny())) + .ReturnsAsync(new Chat { + Model = "test-model", + Messages = new List { + new Message { Content = "Response", Role = "Assistant" } + } + }); + + // Act + var result = await _agentContext.ProcessAsync(message); + + // Assert + Assert.True(result.Done); + Assert.Equal("test-model", result.Model); + Assert.NotNull(result.Message); + } + + [Fact] + public async Task FromExisting_ShouldCreateContextFromExistingAgent() + { + // Arrange + var existingAgentId = "existing-agent-id"; + var existingAgent = new Agent { Id = existingAgentId, Name = "Existing Agent", CurrentBehaviour = "Default", Context = new AgentData() }; + + _mockAgentService + .Setup(s => s.GetAgentById(existingAgentId)) + .ReturnsAsync(existingAgent); + + // Act + var result = await AgentContext.FromExisting(_mockAgentService.Object, existingAgentId); + + // Assert + Assert.NotNull(result); + Assert.Equal(existingAgentId, result.GetAgentId()); + } + + [Fact] + public async Task FromExisting_ShouldThrowArgumentExceptionWhenAgentNotFound() + { + // Arrange + var nonExistentAgentId = "non-existent-agent-id"; + + _mockAgentService + .Setup(s => s.GetAgentById(nonExistentAgentId)) + .ReturnsAsync((Agent)null!); + + // Act & Assert + await Assert.ThrowsAsync(() => + AgentContext.FromExisting(_mockAgentService.Object, nonExistentAgentId)); + } +} \ No newline at end of file diff --git a/src/MaIN.Core.UnitTests/ChatContextTests.cs b/src/MaIN.Core.UnitTests/ChatContextTests.cs index bc47fdff..751e2941 100644 --- a/src/MaIN.Core.UnitTests/ChatContextTests.cs +++ b/src/MaIN.Core.UnitTests/ChatContextTests.cs @@ -75,15 +75,15 @@ public void WithFiles_ShouldAttachFilesToLastMessage() public async Task CompleteAsync_ShouldCallChatService() { // Arrange - var chatResult = new ChatResult(); - _mockChatService.Setup(s => s.Completions(It.IsAny(), It.IsAny(), It.IsAny())) + var chatResult = new ChatResult(){ Model = "test-model", Message = new MessageDto() }; + _mockChatService.Setup(s => s.Completions(It.IsAny(), It.IsAny(), It.IsAny(), null)) .ReturnsAsync(chatResult); // Act var result = await _chatContext.CompleteAsync(); // Assert - _mockChatService.Verify(s => s.Completions(It.IsAny(), false, false), Times.Once); + _mockChatService.Verify(s => s.Completions(It.IsAny(), false, false, null), Times.Once); Assert.Equal(chatResult, result); } diff --git a/src/MaIN.Core.UnitTests/FlowContextTests.cs b/src/MaIN.Core.UnitTests/FlowContextTests.cs new file mode 100644 index 00000000..2135d177 --- /dev/null +++ b/src/MaIN.Core.UnitTests/FlowContextTests.cs @@ -0,0 +1,199 @@ +using MaIN.Core.Hub.Contexts; +using MaIN.Domain.Entities; +using MaIN.Domain.Entities.Agents; +using MaIN.Services.Services.Abstract; +using Moq; +using MaIN.Domain.Entities.Agents.AgentSource; + +namespace MaIN.Core.UnitTests; + +public class FlowContextTests +{ + private readonly Mock _mockFlowService; + private readonly Mock _mockAgentService; + private FlowContext _flowContext; + + public FlowContextTests() + { + _mockFlowService = new Mock(); + _mockAgentService = new Mock(); + _flowContext = new FlowContext(_mockFlowService.Object, _mockAgentService.Object); + } + + [Fact] + public async Task WithId_ShouldSetFlowId() + { + // Arrange + var expectedId = "test-flow-id"; + + // Act + var result = _flowContext.WithId(expectedId); + + // Setup mock to return flow with the set ID + _mockFlowService + .Setup(s => s.GetFlowById(expectedId)) + .ReturnsAsync(new AgentFlow { Id = expectedId, Name = It.IsAny() }); + + var flow = await _flowContext.GetCurrentFlow(); + + // Assert + Assert.Equal(expectedId, flow.Id); + Assert.Equal(result, _flowContext); + } + + [Fact] + public async Task WithName_ShouldSetFlowName() + { + // Arrange + var expectedName = "Test Flow"; + + // Act + var result = _flowContext.WithName(expectedName); + + // Setup mock to return flow with the set name + _mockFlowService + .Setup(s => s.GetFlowById(It.IsAny())) + .ReturnsAsync(new AgentFlow { + Id = It.IsAny(), + Name = expectedName + }); + + var flow = await _flowContext.GetCurrentFlow(); + + // Assert + Assert.Equal(expectedName, flow.Name); + Assert.Equal(result, _flowContext); + } + + [Fact] + public async Task CreateAsync_ShouldCallFlowService() + { + // Arrange + var expectedFlow = new AgentFlow { Id = "new-flow-id", Name = It.IsAny() }; + _mockFlowService + .Setup(s => s.CreateFlow(It.IsAny())) + .ReturnsAsync(expectedFlow); + + // Act + var result = await _flowContext.CreateAsync(); + + // Assert + _mockFlowService.Verify(s => s.CreateFlow(It.IsAny()), Times.Once); + Assert.Equal(expectedFlow, result); + } + + [Fact] + public async Task ProcessAsync_WithStringMessage_ShouldReturnChatResult() + { + // Arrange + var firstAgent = new Agent { Id = "first-agent", Order = 0, CurrentBehaviour = It.IsAny(), Context = new AgentData()}; + _flowContext.AddAgent(firstAgent); + + var message = "Hello, flow!"; + var chat = new Chat { Id = firstAgent.Id, Messages = new List() }; + + _mockAgentService + .Setup(s => s.GetChatByAgent(firstAgent.Id)) + .ReturnsAsync(chat); + + _mockAgentService + .Setup(s => s.Process(It.IsAny(), firstAgent.Id, It.IsAny())) + .ReturnsAsync(new Chat { + Model = "test-model", + Messages = new List { + new() { Content = "Response", Role = "Assistant" } + } + }); + + // Act + var result = await _flowContext.ProcessAsync(message); + + // Assert + Assert.True(result.Done); + Assert.Equal("test-model", result.Model); + Assert.NotNull(result.Message); + } + + [Fact] + public async Task Delete_ShouldCallFlowServiceDelete() + { + // Arrange + var flowId = "test-flow-id"; + _flowContext.WithId(flowId); + + _mockFlowService + .Setup(s => s.DeleteFlow(flowId)) + .Returns(Task.CompletedTask); + + // Act + await _flowContext.Delete(); + + // Assert + _mockFlowService.Verify(s => s.DeleteFlow(flowId), Times.Once); + } + + [Fact] + public async Task GetAllFlows_ShouldReturnListOfFlows() + { + // Arrange + var expectedFlows = new List + { + new() { Id = "flow1", Name = "Flow 1" }, + new() { Id = "flow2", Name = "Flow 2" } + }; + + _mockFlowService + .Setup(s => s.GetAllFlows()) + .ReturnsAsync(expectedFlows); + + // Act + var result = await _flowContext.GetAllFlows(); + + // Assert + Assert.Equal(expectedFlows, result); + } + + [Fact] + public async Task FromExisting_ShouldCreateFlowContextFromExistingFlow() + { + // Arrange + var existingFlowId = "existing-flow-id"; + var existingFlow = new AgentFlow + { + Id = existingFlowId, + Name = "Existing Flow", + Agents = + [ + new Agent + { + Id = "agent1", + CurrentBehaviour = It.IsAny(), + Context = new AgentData() + } + ] + }; + + _mockFlowService + .Setup(s => s.GetFlowById(existingFlowId)) + .ReturnsAsync(existingFlow); + + // Act + var result = await _flowContext.FromExisting(existingFlowId); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task FromExisting_ShouldThrowArgumentExceptionWhenFlowNotFound() + { + // Arrange + var nonExistentFlowId = "non-existent-flow-id"; + _mockFlowService + .Setup(s => s.GetFlowById(nonExistentFlowId)) + .ReturnsAsync((AgentFlow)null!); + + // Act & Assert + await Assert.ThrowsAsync(() => _flowContext.FromExisting(nonExistentFlowId)); + } +} \ No newline at end of file diff --git a/src/MaIN.Core/.nuspec b/src/MaIN.Core/.nuspec index bcd78ce8..e3ff0aba 100644 --- a/src/MaIN.Core/.nuspec +++ b/src/MaIN.Core/.nuspec @@ -2,7 +2,7 @@ MaIN.NET - 0.0.7-pre + 0.0.8-pre Wisedev Wisedev favicon.png diff --git a/src/MaIN.Core/Bootstrapper.cs b/src/MaIN.Core/Bootstrapper.cs index 581fca67..98ceb885 100644 --- a/src/MaIN.Core/Bootstrapper.cs +++ b/src/MaIN.Core/Bootstrapper.cs @@ -32,8 +32,6 @@ public static IServiceProvider UseMaIN(this IServiceProvider sp) public static IServiceCollection AddAIHub(this IServiceCollection services) { - //services.Configure(configureOptions); - // Register core services services.AddSingleton(); services.AddSingleton(); diff --git a/src/MaIN.Core/Hub/Contexts/AgentContext.cs b/src/MaIN.Core/Hub/Contexts/AgentContext.cs index 43bc7279..7fa3c241 100644 --- a/src/MaIN.Core/Hub/Contexts/AgentContext.cs +++ b/src/MaIN.Core/Hub/Contexts/AgentContext.cs @@ -4,7 +4,6 @@ using MaIN.Domain.Models; using MaIN.Services.Mappers; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; using MaIN.Services.Services.Abstract; namespace MaIN.Core.Hub.Contexts; @@ -24,6 +23,7 @@ internal AgentContext(IAgentService agentService) Behaviours = new Dictionary(), Name = $"Agent-{Guid.NewGuid()}", Description = "Agent created by MaIN", + CurrentBehaviour = "Default", Flow = false, Context = new AgentData() { @@ -72,7 +72,7 @@ public AgentContext WithName(string name) return this; } - public AgentContext WithModel(string model) + public AgentContext WithModel(string? model) { _agent.Model = model; return this; @@ -84,7 +84,7 @@ public AgentContext WithInferenceParams(InferenceParams inferenceParams) return this; } - public AgentContext WithCustomModel(string model, string path) + public AgentContext WithCustomModel(string? model, string path) { KnownModels.AddModel(model, path); _agent.Model = model; @@ -98,7 +98,7 @@ public AgentContext WithInitialPrompt(string prompt) return this; } - public AgentContext WithSteps(List steps) + public AgentContext WithSteps(List? steps) { _agent.Context.Steps = steps; return this; @@ -124,7 +124,7 @@ public AgentContext Create(bool flow = false, bool interactiveResponse = false) return this; } - public async Task ProcessAsync(Chat? chat, bool translate = false) + public async Task ProcessAsync(Chat chat, bool translate = false) { var result = await _agentService.Process(chat, _agent.Id, translate); var message = result!.Messages!.LastOrDefault()!.ToDto(); @@ -140,7 +140,7 @@ public async Task ProcessAsync(Chat? chat, bool translate = false) public async Task ProcessAsync(string message, bool translate = false) { var chat = await _agentService.GetChatByAgent(_agent.Id); - chat.Messages?.Add(new Message() + chat?.Messages.Add(new Message() { Content = message, Role = "User", @@ -160,9 +160,9 @@ public async Task ProcessAsync(string message, bool translate = fals public async Task ProcessAsync(Message message, bool translate = false) { var chat = await _agentService.GetChatByAgent(_agent.Id); - chat.Messages?.Add(message); + chat?.Messages.Add(message); var result = await _agentService.Process(chat, _agent.Id, translate); - var messageResult = result!.Messages!.LastOrDefault()!.ToDto(); + var messageResult = result!.Messages.LastOrDefault()!.ToDto(); return new ChatResult() { Done = true, @@ -172,12 +172,12 @@ public async Task ProcessAsync(Message message, bool translate = fal }; } - public async Task GetChat() + public async Task GetChat() { return await _agentService.GetChatByAgent(_agent.Id); } - public async Task RestartChat() + public async Task RestartChat() { return await _agentService.Restart(_agent.Id); } diff --git a/src/MaIN.Core/Hub/Contexts/ChatContext.cs b/src/MaIN.Core/Hub/Contexts/ChatContext.cs index 03ca8819..1bb957cb 100644 --- a/src/MaIN.Core/Hub/Contexts/ChatContext.cs +++ b/src/MaIN.Core/Hub/Contexts/ChatContext.cs @@ -1,7 +1,6 @@ using MaIN.Domain.Entities; using MaIN.Domain.Models; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; using MaIN.Services.Services.Abstract; using FileInfo = MaIN.Domain.Entities.FileInfo; @@ -10,7 +9,7 @@ namespace MaIN.Core.Hub.Contexts; public class ChatContext { private readonly IChatService _chatService; - private Chat? _chat; + private Chat _chat { get; set; } internal ChatContext(IChatService chatService) { @@ -23,13 +22,13 @@ internal ChatContext(IChatService chatService) }; } - internal ChatContext(IChatService chatService, Chat? existingChat) + internal ChatContext(IChatService chatService, Chat existingChat) { _chatService = chatService; _chat = existingChat; } - public ChatContext WithModel(string model) + public ChatContext WithModel(string? model) { _chat.Model = model; return this; @@ -41,7 +40,7 @@ public ChatContext WithInferenceParams(InferenceParams inferenceParams) return this; } - public ChatContext WithCustomModel(string model, string path) + public ChatContext WithCustomModel(string? model, string path) { KnownModels.AddModel(model, path); _chat.Model = model; @@ -108,18 +107,22 @@ public ChatContext EnableVisual() return this; } - public string GetChatId() => _chat.Id; + public string? GetChatId() => _chat.Id; - public async Task CompleteAsync(bool translate = false, bool interactive = false) + public async Task CompleteAsync( + bool translate = false, + bool interactive = false, + Func? changeOfValue = null) { - if (_chat.Id == null || !await ChatExists(_chat.Id)) + if (!await ChatExists(_chat.Id)) { await _chatService.Create(_chat); } - return await _chatService.Completions(_chat, translate, interactive); + return await _chatService.Completions(_chat, translate, interactive, changeOfValue); } + - public async Task GetCurrentChat() + public async Task GetCurrentChat() { if (_chat.Id == null) throw new InvalidOperationException("Chat has not been created yet. Call CompleteAsync first."); @@ -140,7 +143,7 @@ public async Task DeleteChat() await _chatService.Delete(_chat.Id); } - private async Task ChatExists(string id) + private async Task ChatExists(string? id) { try { @@ -154,9 +157,13 @@ private async Task ChatExists(string id) } // Static methods to create builder from existing chat - public async Task FromExisting(string chatId) + public async Task FromExisting(string? chatId) { var existingChat = await _chatService.GetById(chatId); + if (existingChat == null) + { + throw new Exception("Chat not found"); + } return new ChatContext(_chatService, existingChat); } diff --git a/src/MaIN.Core/Hub/Contexts/FlowContext.cs b/src/MaIN.Core/Hub/Contexts/FlowContext.cs index 235e87ac..938176d7 100644 --- a/src/MaIN.Core/Hub/Contexts/FlowContext.cs +++ b/src/MaIN.Core/Hub/Contexts/FlowContext.cs @@ -23,6 +23,7 @@ internal FlowContext(IAgentFlowService flowService, IAgentService agentService) _flow = new AgentFlow { Id = Guid.NewGuid().ToString(), + Name = string.Empty, Agents = new List(), }; } @@ -54,7 +55,7 @@ public FlowContext WithDescription(string description) public FlowContext Save(string path) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); using (var fileStream = new FileStream(path, FileMode.Create)) using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create)) @@ -138,7 +139,7 @@ public FlowContext AddAgent(Agent agent) } - public async Task ProcessAsync(Chat? chat, bool translate = false) + public async Task ProcessAsync(Chat chat, bool translate = false) { var result = await _agentService.Process(chat, _firstAgent!.Id, translate); var message = result!.Messages!.LastOrDefault()!.ToDto(); @@ -154,7 +155,7 @@ public async Task ProcessAsync(Chat? chat, bool translate = false) public async Task ProcessAsync(string message, bool translate = false) { var chat = await _agentService.GetChatByAgent(_firstAgent!.Id); - chat.Messages?.Add(new Message() + chat?.Messages.Add(new Message() { Content = message, Role = "User", @@ -174,9 +175,9 @@ public async Task ProcessAsync(string message, bool translate = fals public async Task ProcessAsync(Message message, bool translate = false) { var chat = await _agentService.GetChatByAgent(_firstAgent!.Id); - chat.Messages?.Add(message); + chat?.Messages.Add(message); var result = await _agentService.Process(chat, _firstAgent.Id, translate); - var messageResult = result!.Messages!.LastOrDefault()!.ToDto(); + var messageResult = result!.Messages.LastOrDefault()!.ToDto(); return new ChatResult() { Done = true, diff --git a/src/MaIN.Core/Hub/Utils/StepBuilder.cs b/src/MaIN.Core/Hub/Utils/StepBuilder.cs index 2f661a40..ad079073 100644 --- a/src/MaIN.Core/Hub/Utils/StepBuilder.cs +++ b/src/MaIN.Core/Hub/Utils/StepBuilder.cs @@ -2,7 +2,7 @@ namespace MaIN.Core.Hub.Utils; public class StepBuilder { - public List Steps = new(); + private readonly List Steps = new(); public static StepBuilder Instance => new(); public StepBuilder Answer() diff --git a/src/MaIN.Domain/Entities/Agents/Agent.cs b/src/MaIN.Domain/Entities/Agents/Agent.cs index 62591710..6e60a830 100644 --- a/src/MaIN.Domain/Entities/Agents/Agent.cs +++ b/src/MaIN.Domain/Entities/Agents/Agent.cs @@ -2,15 +2,15 @@ namespace MaIN.Domain.Entities.Agents; public class Agent { - public string Id { get; set; } - public string Name { get; set; } - public string Model { get; set; } - public string? Description { get; set; } + public required string Id { get; set; } + public string Name { get; set; } = null!; + public string? Model { get; set; } + public string? Description { get; init; } public bool Started { get; set; } public bool Flow { get; set; } - public AgentData Context { get; set; } - public string? ChatId { get; set; } + public required AgentData Context { get; init; } + public string? ChatId { get; init; } public int Order { get; set; } public Dictionary? Behaviours { get; set; } - public string CurrentBehaviour { get; set; } + public required string CurrentBehaviour { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentData.cs b/src/MaIN.Domain/Entities/Agents/AgentData.cs index faed9dac..41b78a06 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentData.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentData.cs @@ -2,9 +2,9 @@ namespace MaIN.Domain.Entities.Agents; public class AgentData { - public string Instruction { get; set; } + public string? Instruction { get; set; } public AgentSource.AgentSource? Source { get; set; } - public List Steps { get; set; } + public List? Steps { get; set; } public List? Relations { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentRelations.cs b/src/MaIN.Domain/Entities/Agents/AgentRelations.cs deleted file mode 100644 index c38972b7..00000000 --- a/src/MaIN.Domain/Entities/Agents/AgentRelations.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MaIN.Domain.Entities.Agents; - -public class AgentRelation -{ - public string Id { get; set; } -} \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentAPISourceDetails.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentAPISourceDetails.cs index 8aceabef..0aac91c3 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentAPISourceDetails.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentAPISourceDetails.cs @@ -2,10 +2,10 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentApiSourceDetails : AgentSourceDetailsBase, IAgentSource { - public string Url { get; set; } - public string Method { get; set; } + public required string Url { get; set; } + public required string Method { get; init; } public string? Payload { get; set; } public string? Query { get; set; } - public string ResponseType { get; set; } - public int? ChunkLimit { get; set; } + public string? ResponseType { get; init; } + public int? ChunkLimit { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFileSourceDetails.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFileSourceDetails.cs index b4c027bd..8207dd0c 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFileSourceDetails.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFileSourceDetails.cs @@ -2,6 +2,6 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentFileSourceDetails : AgentSourceDetailsBase, IAgentSource { - public string Path { get; set; } - public string Name { get; set; } + public required string Path { get; init; } + public required string Name { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFlow.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFlow.cs index dedb740b..a9e2d085 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFlow.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentFlow.cs @@ -3,7 +3,7 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentFlow { public string? Id { get; set; } - public string Name { get; set; } - public List Agents { get; set; } - public string Description { get; set; } + public required string Name { get; set; } + public List Agents { get; init; } = []; + public string? Description { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentNoSqlSourceDetails.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentNoSqlSourceDetails.cs index 510c57c5..6c05792d 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentNoSqlSourceDetails.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentNoSqlSourceDetails.cs @@ -2,8 +2,8 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentNoSqlSourceDetails : IAgentSource { - public string ConnectionString { get; set; } - public string DbName { get; set; } - public string Collection { get; set; } - public string Query { get; set; } + public required string ConnectionString { get; set; } + public required string DbName { get; init; } + public required string Collection { get; set; } + public required string Query { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentSqlSourceDetails.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentSqlSourceDetails.cs index 41d080b0..ac621d89 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentSqlSourceDetails.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentSqlSourceDetails.cs @@ -2,7 +2,7 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentSqlSourceDetails : IAgentSource { - public string ConnectionString { get; set; } - public string Table { get; set; } - public string Query { get; set; } + public required string ConnectionString { get; set; } + public required string Table { get; init; } + public required string Query { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentTextSourceDetails.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentTextSourceDetails.cs index 7d78a79e..94436e2d 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentTextSourceDetails.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentTextSourceDetails.cs @@ -2,5 +2,5 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentTextSourceDetails : AgentSourceDetailsBase, IAgentSource { - public string Text { get; set; } + public required string Text { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentWebSourceDetails.cs b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentWebSourceDetails.cs index c936bb29..3937d115 100644 --- a/src/MaIN.Domain/Entities/Agents/AgentSource/AgentWebSourceDetails.cs +++ b/src/MaIN.Domain/Entities/Agents/AgentSource/AgentWebSourceDetails.cs @@ -2,5 +2,5 @@ namespace MaIN.Domain.Entities.Agents.AgentSource; public class AgentWebSourceDetails : AgentSourceDetailsBase, IAgentSource { - public string Url { get; set; } + public required string Url { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/Commands/Base/BaseCommand.cs b/src/MaIN.Domain/Entities/Agents/Commands/Base/BaseCommand.cs index fe32e56a..aa37bce5 100644 --- a/src/MaIN.Domain/Entities/Agents/Commands/Base/BaseCommand.cs +++ b/src/MaIN.Domain/Entities/Agents/Commands/Base/BaseCommand.cs @@ -2,5 +2,5 @@ namespace MaIN.Domain.Entities.Agents.Commands.Base; public class BaseCommand { - public Chat? Chat { get; set; } + public Chat Chat { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/Commands/BecomeCommand.cs b/src/MaIN.Domain/Entities/Agents/Commands/BecomeCommand.cs index 5ea24dc0..aad64e5c 100644 --- a/src/MaIN.Domain/Entities/Agents/Commands/BecomeCommand.cs +++ b/src/MaIN.Domain/Entities/Agents/Commands/BecomeCommand.cs @@ -4,5 +4,5 @@ namespace MaIN.Domain.Entities.Agents.Commands; public class BecomeCommand : BaseCommand { - public string Key { get; set; } + public required string Key { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/Commands/FetchCommand.cs b/src/MaIN.Domain/Entities/Agents/Commands/FetchCommand.cs index e03676a6..08b2158f 100644 --- a/src/MaIN.Domain/Entities/Agents/Commands/FetchCommand.cs +++ b/src/MaIN.Domain/Entities/Agents/Commands/FetchCommand.cs @@ -4,6 +4,6 @@ namespace MaIN.Domain.Entities.Agents.Commands; public class FetchCommand : BaseCommand { - public string? Filter { get; set; } - public AgentData Context { get; set; } + public string? Filter { get; init; } + public required AgentData Context { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Agents/Commands/RedirectCommand.cs b/src/MaIN.Domain/Entities/Agents/Commands/RedirectCommand.cs index 20c8b299..bbe18a18 100644 --- a/src/MaIN.Domain/Entities/Agents/Commands/RedirectCommand.cs +++ b/src/MaIN.Domain/Entities/Agents/Commands/RedirectCommand.cs @@ -4,10 +4,10 @@ namespace MaIN.Domain.Entities.Agents.Commands; public class RedirectCommand : BaseCommand { - public Message Message { get; set; } - public string RelatedAgentId { get; set; } = null!; - public OutputTypeOfRedirect SaveAs { get; set; } - public string? Filter { get; set; } + public required Message Message { get; init; } + public required string RelatedAgentId { get; init; } + public OutputTypeOfRedirect SaveAs { get; init; } + public string? Filter { get; init; } } public enum OutputTypeOfRedirect diff --git a/src/MaIN.Domain/Entities/Agents/Commands/StartCommand.cs b/src/MaIN.Domain/Entities/Agents/Commands/StartCommand.cs index 538b3b43..afeb50b7 100644 --- a/src/MaIN.Domain/Entities/Agents/Commands/StartCommand.cs +++ b/src/MaIN.Domain/Entities/Agents/Commands/StartCommand.cs @@ -4,5 +4,5 @@ namespace MaIN.Domain.Entities.Agents.Commands; public class StartCommand : BaseCommand { - public string InitialPrompt { get; set; } + public string? InitialPrompt { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Chat.cs b/src/MaIN.Domain/Entities/Chat.cs index f2dbf324..cfe029ab 100644 --- a/src/MaIN.Domain/Entities/Chat.cs +++ b/src/MaIN.Domain/Entities/Chat.cs @@ -2,15 +2,15 @@ namespace MaIN.Domain.Entities; public class Chat { - public string Id { get; set; } - public string Name { get; set; } - public string Model { get; set; } + public string Id { get; init; } = null!; + public string? Name { get; init; } + public string? Model { get; set; } public List Messages { get; set; } = []; public ChatType Type { get; set; } = ChatType.Conversation; - public bool Visual { get; set; } = false; + public bool Visual { get; set; } public InferenceParams InterferenceParams { get; set; } = new(); - public Dictionary Properties { get; set; } = []; - public List Memory { get; set; } = []; + public Dictionary? Properties { get; init; } = []; + public List Memory { get; } = []; public bool Interactive = false; public bool Translate = false; diff --git a/src/MaIN.Domain/Entities/InferenceParams.cs b/src/MaIN.Domain/Entities/InferenceParams.cs index bb361433..060f5559 100644 --- a/src/MaIN.Domain/Entities/InferenceParams.cs +++ b/src/MaIN.Domain/Entities/InferenceParams.cs @@ -2,6 +2,6 @@ namespace MaIN.Domain.Entities; public class InferenceParams { - public float Temperature { get; set; } = 0.8f; - public int ContextSize { get; set; } = 1024; + public float Temperature { get; init; } = 0.8f; + public int ContextSize { get; init; } = 1024; } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/Message.cs b/src/MaIN.Domain/Entities/Message.cs index 2ad09541..24c8a743 100644 --- a/src/MaIN.Domain/Entities/Message.cs +++ b/src/MaIN.Domain/Entities/Message.cs @@ -2,11 +2,11 @@ namespace MaIN.Domain.Entities; public class Message { - public string Role { get; set; } - public string Content { get; set; } - public bool Tool { get; set; } + public required string Role { get; set; } + public required string Content { get; set; } + public bool Tool { get; init; } public DateTime Time { get; set; } - public byte[] Images { get; set; } + public byte[]? Images { get; init; } public List? Files { get; set; } //Temporary solution - public Dictionary Properties { get; set; } = []; + public Dictionary Properties { get; set; } = []; } \ No newline at end of file diff --git a/src/MaIN.Domain/Entities/MessageShort.cs b/src/MaIN.Domain/Entities/MessageShort.cs index 7204a492..63efaf4e 100644 --- a/src/MaIN.Domain/Entities/MessageShort.cs +++ b/src/MaIN.Domain/Entities/MessageShort.cs @@ -2,7 +2,7 @@ namespace MaIN.Domain.Entities; public class MessageShort { - public string Role { get; set; } - public string Content { get; set; } + public required string Role { get; init; } + public required string Content { get; init; } public DateTime Time { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Domain/Models/SupportedModels.cs b/src/MaIN.Domain/Models/SupportedModels.cs index c2f0a9bd..b8903e9b 100644 --- a/src/MaIN.Domain/Models/SupportedModels.cs +++ b/src/MaIN.Domain/Models/SupportedModels.cs @@ -1,105 +1,98 @@ namespace MaIN.Domain.Models; -public struct Model +public class Model { - public string Name { get; set; } - public string FileName { get; set; } - public string DownloadUrl { get; set; } - public string Description { get; set; } - public string Path { get; set; } + public required string? Name { get; init; } + public required string FileName { get; init; } + public string? DownloadUrl { get; set; } + public string? Description { get; set; } + public string? Path { get; set; } } -public struct KnownModels +public static class KnownModels { - internal static Dictionary Models => new(StringComparer.OrdinalIgnoreCase) - { + private static List Models { get; } = + [ + new Model() { - KnownModelNames.Gemma2_2b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Gemma2_2b, - FileName = "gemma2-2b-maIN.gguf", - DownloadUrl = "https://huggingface.co/TheBloke/gemma2-2b-quantized/resolve/main/gemma2-2b-quantized.bin", - } + Description = string.Empty, + Name = KnownModelNames.Gemma2_2b, + FileName = "gemma2-2b.gguf", + DownloadUrl = "https://huggingface.co/TheBloke/gemma2-2b-quantized/resolve/main/gemma2-2b-quantized.bin", }, + + new Model() { - KnownModelNames.Llama3_2_3b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Llama3_2_3b, - FileName = "Llama3.2-maIN.gguf", - DownloadUrl = string.Empty - } + Description = string.Empty, + Name = KnownModelNames.Llama3_2_3b, + FileName = "Llama3.2-3b.gguf", + DownloadUrl = string.Empty }, + + new Model() { - KnownModelNames.Llama3_1_8b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Llama3_1_8b, - FileName = "Llama3.1-maIN.gguf", - DownloadUrl = string.Empty - } + Description = string.Empty, + Name = KnownModelNames.Llama3_1_8b, + FileName = "Llama3.1-8b.gguf", + DownloadUrl = string.Empty }, + + new Model() { - KnownModelNames.Llava_7b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Llava_7b, - FileName = "Llava-maIN.gguf", - DownloadUrl = string.Empty, - } + Description = string.Empty, + Name = KnownModelNames.Llava_7b, + FileName = "Llava.gguf", + DownloadUrl = string.Empty, }, + + new Model() { - KnownModelNames.Phi_mini, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Phi_mini, - FileName = "phi3.5-maIN.gguf", - DownloadUrl = string.Empty - } + Description = string.Empty, + Name = KnownModelNames.Phi_mini, + FileName = "phi3.5.gguf", + DownloadUrl = string.Empty }, + + new Model() { - KnownModelNames.Qwen2_5_0_5b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Qwen2_5_0_5b, - FileName = "Qwen2.5-maIN.gguf", - DownloadUrl = string.Empty - } + Description = string.Empty, + Name = KnownModelNames.Qwen2_5_0_5b, + FileName = "Qwen2.5.gguf", + DownloadUrl = string.Empty }, + + new Model() { - KnownModelNames.DeepSeek_R1_8b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.DeepSeek_R1_8b, - FileName = "DeepSeekR1-8b-maIN.gguf", - DownloadUrl = string.Empty - } + Description = string.Empty, + Name = KnownModelNames.DeepSeek_R1_8b, + FileName = "DeepSeekR1-8b.gguf", + DownloadUrl = string.Empty }, + + new Model() { - KnownModelNames.Fox_1_6b, new Model() - { - Description = string.Empty, - Name = KnownModelNames.Fox_1_6b, - FileName = "Fox-1.6b-maIN.gguf", - DownloadUrl = string.Empty - } - }, - }; + Description = string.Empty, + Name = KnownModelNames.Fox_1_6b, + FileName = "Fox-1.6b.gguf", + DownloadUrl = string.Empty + } + ]; public static Model GetEmbeddingModel() => new() { Name = KnownModelNames.Nomic_Embedding, - FileName = "nomic-maIN.gguf", + FileName = "nomic.gguf", Description = "Model used to generate embeddings.", DownloadUrl = string.Empty, }; - public static Model GetModel(string path, string name) + public static Model GetModel(string path, string? name) { - var isPresent = Models.TryGetValue(name, out var model); - if (!isPresent) + var model = Models.FirstOrDefault(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase) + || x.Name.Replace(':', '-').Equals(name, + StringComparison.InvariantCultureIgnoreCase)); + if (model is null) { //todo support domain specific exceptions throw new Exception($"Model {name} is not supported"); @@ -107,7 +100,7 @@ public static Model GetModel(string path, string name) if (File.Exists(Path.Combine(path, model.FileName))) { - return Models[name]; + return model; } throw new Exception($"Model {name} is not downloaded"); @@ -115,8 +108,7 @@ public static Model GetModel(string path, string name) public static Model? GetModelByFileName(string path, string fileName) { - var models = Models.Values.ToList(); - var isPresent = models.Exists(x => x.FileName == fileName); + var isPresent = Models.Exists(x => x.FileName == fileName); if (!isPresent) { //todo support domain specific exceptions @@ -126,15 +118,15 @@ public static Model GetModel(string path, string name) if (File.Exists(Path.Combine(path, fileName))) { - return models.First(x => x.FileName == fileName); + return Models.First(x => x.FileName == fileName); } throw new Exception($"Model {fileName} is not downloaded"); } - public static void AddModel(string model, string path) + public static void AddModel(string? model, string path) { - Models.Add(model, new Model() + Models.Add(new Model() { Description = string.Empty, DownloadUrl = string.Empty, @@ -154,6 +146,6 @@ public struct KnownModelNames public const string Phi_mini = "phi3:mini"; public const string Llava_7b = "llava:7b"; public const string Qwen2_5_0_5b = "qwen2.5:0.5b"; - public const string DeepSeek_R1_8b = "deepseek:r1_8b"; + public const string DeepSeek_R1_8b = "deepseekR1-8b"; public const string Fox_1_6b = "fox:1.6b"; } \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/App.razor b/src/MaIN.InferPage/Components/App.razor new file mode 100644 index 00000000..2543f9aa --- /dev/null +++ b/src/MaIN.InferPage/Components/App.razor @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/Layout/MainLayout.razor b/src/MaIN.InferPage/Components/Layout/MainLayout.razor new file mode 100644 index 00000000..fdf39a6d --- /dev/null +++ b/src/MaIN.InferPage/Components/Layout/MainLayout.razor @@ -0,0 +1,14 @@ +@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular +@inherits LayoutComponentBase + + +
+ +@Body +
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/src/MaIN.InferPage/Components/Layout/NavBar.razor b/src/MaIN.InferPage/Components/Layout/NavBar.razor new file mode 100644 index 00000000..c7d4c2f4 --- /dev/null +++ b/src/MaIN.InferPage/Components/Layout/NavBar.razor @@ -0,0 +1,43 @@ +@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular +@using Size32 = Microsoft.FluentUI.AspNetCore.Components.Icons.Filled.Size32 +@inject NavigationManager _navigationManager +@rendermode @(new InteractiveServerRenderMode(prerender: false)) + + + + + +@code { + private DesignThemeModes Mode { get; set; } + + private void SetTheme() + { + Mode = Mode == DesignThemeModes.Dark ? DesignThemeModes.Light : DesignThemeModes.Dark; + } + + private void Reload(MouseEventArgs obj) + { + _navigationManager.Refresh(true); + } + +} \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/Pages/Error.razor b/src/MaIN.InferPage/Components/Pages/Error.razor new file mode 100644 index 00000000..9d7c6be8 --- /dev/null +++ b/src/MaIN.InferPage/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; + +} \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/Pages/Home.razor b/src/MaIN.InferPage/Components/Pages/Home.razor new file mode 100644 index 00000000..fec64b49 --- /dev/null +++ b/src/MaIN.InferPage/Components/Pages/Home.razor @@ -0,0 +1,183 @@ +@page "/" +@rendermode @(new InteractiveServerRenderMode(prerender: true)) +@inject IJSRuntime JS +@using MaIN.Core.Hub +@using MaIN.Core.Hub.Contexts +@using MaIN.Domain.Entities +@using Markdig +@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular +@using Message = MaIN.Domain.Entities.Message + + +MaIN Infer + + + + +
+ @foreach (var message in Chat.Messages) + { +
+ @if (message.Role != "System") + { + @if (Chat.Visual) + { + + @(message.Role == "User" ? "User" : Utils.Model) + + @if (message.Role == "User") + { + + @message.Content + + } + else + { + +
+ + imageResponse + +
+
+ } + } + else + { + + @(message.Role == "User" ? "User" : Utils.Model) + + + @((MarkupString)((message.Role == "User" + ? message.Content + : Markdown.ToHtml((string)message.Content!, + new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build())) ?? string.Empty)) + + } + } + } + @if (_isLoading) + { + @if (Chat.Visual) + { + @_displayName + This might take a while... + + } + else + { + + @_displayName + + + @if (_incomingMessage != null) + { + + @((MarkupString)Markdown.ToHtml(_incomingMessage.ToString()!, + new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build())) + + } + } + } +
+
+
+ + + + + +
+
+ +
+ + +@* ReSharper disable once UnassignedField.Compiler *@ +@code { + private string? _prompt; + private bool _isLoading; + private string? _incomingMessage = null; + private readonly string? _displayName = Utils.Model; + private ChatContext? ctx; + private Chat Chat { get; set; } = new(); + private ElementReference? _bottomElement; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + await JS.InvokeVoidAsync("scrollManager.restoreScrollPosition", "messages-container"); + } + } + + protected override Task OnInitializedAsync() + { + ctx = Utils.Visual ? AIHub.Chat().EnableVisual() + : AIHub.Chat().WithModel(Utils.Model); //If that grows with different chat types we can consider switch ex + return base.OnInitializedAsync(); + } + + private async Task CheckEnterKey(KeyboardEventArgs e) + { + if (e.Key == "Enter") + { + await SendAsync(_prompt!); + } + } + + private async Task SendAsync(string msg) + { + if (!string.IsNullOrWhiteSpace(msg)) + { + var newMsg = new Message { Role = "User", Content = msg }; + Chat.Messages.Add(newMsg); + Chat.Model = Utils.Model; + _isLoading = true; + Chat.Visual = Utils.Visual; + _prompt = string.Empty; + StateHasChanged(); + bool wasAtBottom = await JS.InvokeAsync("scrollManager.isAtBottom", "messages-container"); + await ctx!.WithMessage(msg) + .CompleteAsync(changeOfValue: async message => + { + _incomingMessage += message; + StateHasChanged(); + if (wasAtBottom) + { + await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement); + } + }); + + _isLoading = false; + var currentChat = (await ctx.GetCurrentChat())!; + Chat.Messages.Add(currentChat.Messages.Last()); + _incomingMessage = null; + await JS.InvokeVoidAsync("scrollManager.scrollToBottomSmooth", _bottomElement); + StateHasChanged(); + + } + } + + private void Callback(ChangeEventArgs obj) + { + _prompt = obj.Value?.ToString()!; + } + +} \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/Routes.razor b/src/MaIN.InferPage/Components/Routes.razor new file mode 100644 index 00000000..ae94e9e9 --- /dev/null +++ b/src/MaIN.InferPage/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/MaIN.InferPage/Components/_Imports.razor b/src/MaIN.InferPage/Components/_Imports.razor new file mode 100644 index 00000000..a7b02a1b --- /dev/null +++ b/src/MaIN.InferPage/Components/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.FluentUI.AspNetCore.Components +@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons +@using Microsoft.JSInterop +@using MaIN.InferPage +@using MaIN.InferPage.Components \ No newline at end of file diff --git a/src/MaIN.InferPage/FodyWeavers.xml b/src/MaIN.InferPage/FodyWeavers.xml new file mode 100644 index 00000000..0031198e --- /dev/null +++ b/src/MaIN.InferPage/FodyWeavers.xml @@ -0,0 +1,8 @@ + + + + appsettings.json + true + true + + \ No newline at end of file diff --git a/src/MaIN.InferPage/MaIN.InferPage.csproj b/src/MaIN.InferPage/MaIN.InferPage.csproj new file mode 100644 index 00000000..3e8cfacb --- /dev/null +++ b/src/MaIN.InferPage/MaIN.InferPage.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + false + + + + + all + + + + + + + + + + + + + + diff --git a/src/MaIN.InferPage/Program.cs b/src/MaIN.InferPage/Program.cs new file mode 100644 index 00000000..4f93478e --- /dev/null +++ b/src/MaIN.InferPage/Program.cs @@ -0,0 +1,96 @@ +using MaIN.Core; +using MaIN.Domain.Configuration; +using Microsoft.FluentUI.AspNetCore.Components; +using MaIN.InferPage.Components; +using Utils = MaIN.InferPage.Utils; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); +builder.Services.AddFluentUIComponents(); +// --- Additional Parameter Processing Logic --- + +try +{ + // Retrieve parameters from configuration (e.g. command-line arguments) + var modelArg = builder.Configuration["model"]; + var modelPathArg = builder.Configuration["path"]; + var backendArg = builder.Configuration["backend"]; + bool openAiFlag = backendArg != null && backendArg.Equals("openai", StringComparison.OrdinalIgnoreCase); + + if (!string.IsNullOrEmpty(modelArg)) + { + // Set the model value + Utils.Model = modelArg; + + // A model path is required if a model is provided + if (string.IsNullOrEmpty(modelPathArg)) + { + Console.WriteLine("Error: A model path must be provided using --path when a model is specified."); + return; + } + Utils.Path = modelPathArg; + + // Check if the environment variable for models path is set; if not, prompt the user to input it + var envModelsPath = Environment.GetEnvironmentVariable("MaIN_ModelsPath"); + if (string.IsNullOrEmpty(envModelsPath)) + { + Console.Write("Please enter the MaIN_ModelsPath: "); + envModelsPath = Console.ReadLine(); + Environment.SetEnvironmentVariable("MaIN_ModelsPath", envModelsPath); + } + } + else + { + Console.WriteLine("No model argument provided. Continuing without model configuration."); + } + + if (openAiFlag) + { + Utils.OpenAi = true; + // Check if the OpenAI key is set in the environment variables + var openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + if (string.IsNullOrEmpty(openAiKey)) + { + Console.Write("Please enter your OpenAI API key: "); + openAiKey = Console.ReadLine(); + Environment.SetEnvironmentVariable("OPENAI_API_KEY", openAiKey ); + } + } +} +catch (Exception ex) +{ + Console.WriteLine("Error during parameter processing: " + ex.Message); + return; +} + +if (Utils.OpenAi) +{ + builder.Services.AddMaIN(builder.Configuration, settings => + { + settings.BackendType = BackendType.OpenAi; + }); +} +else +{ + builder.Services.AddMaIN(builder.Configuration); +} + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseAntiforgery(); +app.Services.UseMaIN(); +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + + +app.Run(); diff --git a/src/MaIN.InferPage/Properties/launchSettings.json b/src/MaIN.InferPage/Properties/launchSettings.json new file mode 100644 index 00000000..59771ddd --- /dev/null +++ b/src/MaIN.InferPage/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7113;http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/src/MaIN.InferPage/Utils.cs b/src/MaIN.InferPage/Utils.cs new file mode 100644 index 00000000..da463a91 --- /dev/null +++ b/src/MaIN.InferPage/Utils.cs @@ -0,0 +1,12 @@ +using MaIN.Domain.Entities; + +namespace MaIN.InferPage; + +public static class Utils +{ + public static string? Model = "gemma2:2b"; + public static bool Visual => VisualModels.Contains(Model); + private static readonly string[] VisualModels = ["FLUX.1_Shnell", "dall-e-3"]; + public static bool OpenAi { get; set; } + public static string? Path { get; set; } +} diff --git a/src/MaIN.InferPage/appsettings.Development.json b/src/MaIN.InferPage/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/src/MaIN.InferPage/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/MaIN.InferPage/appsettings.json b/src/MaIN.InferPage/appsettings.json new file mode 100644 index 00000000..1a3f1b7d --- /dev/null +++ b/src/MaIN.InferPage/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Kestrel": { + "EndPoints": { + "Http": { + "Url": "http://localhost:5555" + } + } + }, + "AllowedHosts": "*" +} diff --git a/src/MaIN.InferPage/wwwroot/app.css b/src/MaIN.InferPage/wwwroot/app.css new file mode 100644 index 00000000..f2f5dd6a --- /dev/null +++ b/src/MaIN.InferPage/wwwroot/app.css @@ -0,0 +1,225 @@ +@import url('https://fonts.googleapis.com/css2?family=Tiny5&display=swap'); /* Importing a coding font */ + + +body { + --body-font: "Segoe UI Variable", "Segoe UI", sans-serif; + font-family: var(--body-font); + font-size: var(--type-ramp-base-font-size); + line-height: var(--type-ramp-base-line-height); + margin: 0; +} + +.navmenu-icon { + display: none; +} + +.main { + min-height: calc(100dvh - 86px); + color: var(--neutral-foreground-rest); + align-items: stretch !important; +} + +.body-content { + align-self: stretch; + height: calc(100dvh - 86px) !important; + display: flex; +} + +.content { + padding: 0.5rem 1.5rem; + align-self: stretch !important; + width: 100%; +} + +.manage { + width: 100dvw; +} + +footer { + background: var(--neutral-layer-4); + color: var(--neutral-foreground-rest); + align-items: center; + padding: 10px 10px; +} + + footer a { + color: var(--neutral-foreground-rest); + text-decoration: none; + } + + footer a:focus { + outline: 1px dashed; + outline-offset: 3px; + } + + footer a:hover { + text-decoration: underline; + } + +.alert { + border: 1px dashed var(--accent-fill-rest); + padding: 5px; +} + + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; + margin: 20px 0; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::before { + content: "An error has occurred. " + } + +.loading-progress { + position: relative; + display: block; + width: 8rem; + height: 8rem; + margin: 20vh auto 1rem auto; +} + + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } + + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } + +.loading-progress-text { + position: absolute; + text-align: center; + font-weight: bold; + inset: calc(20vh + 3.25rem) 0 auto 0.2rem; +} + + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } + +code { + color: #c02d76; +} + +@media (max-width: 600px) { + .header-gutters { + margin: 0.5rem 3rem 0.5rem 1.5rem !important; + } + + [dir="rtl"] .header-gutters { + margin: 0.5rem 1.5rem 0.5rem 3rem !important; + } + + .main { + flex-direction: column !important; + row-gap: 0 !important; + } + + nav.sitenav { + width: 100%; + height: 100%; + } + + #main-menu { + width: 100% !important; + } + + #main-menu > div:first-child:is(.expander) { + display: none; + } + + .navmenu { + width: 100%; + } + + #navmenu-toggle { + appearance: none; + } + + #navmenu-toggle ~ nav { + display: none; + } + + #navmenu-toggle:checked ~ nav { + display: block; + } + + .navmenu-icon { + cursor: pointer; + z-index: 10; + display: block; + position: absolute; + top: 15px; + left: unset; + right: 20px; + width: 20px; + height: 20px; + border: none; + } + + [dir="rtl"] .navmenu-icon { + left: 20px; + right: unset; + } +} + +.round-button { + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--button-bg); + color: var(--button-color); + border: none; + cursor: pointer; + transition: background 0.3s ease, transform 0.2s; +} + +.fancy-text { + font-family: "Tiny5", sans-serif; + font-weight: 400; + font-size: 45px; + font-style: normal; + color: var(--accent-fill-rest); + text-shadow: 0 0 5px #00ffcc; +} + +.navbar { + background-color: var(--neutral-layer-1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 20px; + display: flex; + border-bottom: var(--neutral-layer-5) solid 1px; +} + + diff --git a/src/MaIN.InferPage/wwwroot/home.css b/src/MaIN.InferPage/wwwroot/home.css new file mode 100644 index 00000000..8030ce6d --- /dev/null +++ b/src/MaIN.InferPage/wwwroot/home.css @@ -0,0 +1,53 @@ +.input-container { + width: 100%; + display: flex; + padding: 5px; +} + +.messages-container { + flex-grow: 1; + overflow-y: auto; + padding: 10px; + max-height: 80vh; + min-height: 80vh; + display: flex; + flex-direction: column; +} + +.message-role-bot { + align-self: flex-start; + margin-bottom: 5px; +} + +.message-role-user { + margin-bottom: 5px; + align-self: flex-end; +} + +.user-message { + align-self: flex-end; + background-color: var(--neutral-layer-1) !important; +} + +.bot-message { + align-self: flex-start; + background-color: var(--neutral-layer-5) !important; +} + +.message-card { + margin-bottom: 15px; + width: 80%; + height: fit-content; + padding: 10px; +} + +.message-card-img { + margin-bottom: 15px; + width: 80%; + height: fit-content; + padding: 1px !important; +} + +.message-card p { + margin: 0; +} \ No newline at end of file diff --git a/src/MaIN.InferPage/wwwroot/scroll.js b/src/MaIN.InferPage/wwwroot/scroll.js new file mode 100644 index 00000000..a2bc21a9 --- /dev/null +++ b/src/MaIN.InferPage/wwwroot/scroll.js @@ -0,0 +1,42 @@ +window.scrollManager = { + isUserScrolling: false, + + saveScrollPosition: (containerId) => { + const container = document.getElementById(containerId); + if (!container) return; + sessionStorage.setItem("scrollTop", container.scrollTop); + }, + + restoreScrollPosition: (containerId) => { + const container = document.getElementById(containerId); + if (!container) return; + container.scrollTop = 9999; + }, + + isAtBottom: (containerId) => { + const container = document.getElementById(containerId); + if (!container) return false; + return container.scrollHeight - container.scrollTop <= container.clientHeight + 50; + }, + + scrollToBottomSmooth: (bottomElement) => { + if (!bottomElement) return; + if (!window.scrollManager.isUserScrolling) { + bottomElement.scrollIntoView({ behavior: 'smooth' }); + } + }, + + attachScrollListener: (containerId) => { + const container = document.getElementById(containerId); + if (!container) return; + + container.addEventListener("scroll", () => { + window.scrollManager.isUserScrolling = + container.scrollHeight - container.scrollTop > container.clientHeight + 50; + }); + } +}; + +document.addEventListener("DOMContentLoaded", () => { + window.scrollManager.attachScrollListener("bottom"); +}); diff --git a/src/MaIN.Infrastructure/Bootstrapper.cs b/src/MaIN.Infrastructure/Bootstrapper.cs index 25e9ad64..6c9b4e93 100644 --- a/src/MaIN.Infrastructure/Bootstrapper.cs +++ b/src/MaIN.Infrastructure/Bootstrapper.cs @@ -3,7 +3,6 @@ using MaIN.Infrastructure.Repositories; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Infrastructure.Repositories.FileSystem; -using MaIN.Services.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; diff --git a/src/MaIN.Infrastructure/Models/AgentContextDocument.cs b/src/MaIN.Infrastructure/Models/AgentContextDocument.cs index fa4a3853..e466a0c9 100644 --- a/src/MaIN.Infrastructure/Models/AgentContextDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentContextDocument.cs @@ -4,8 +4,8 @@ namespace MaIN.Infrastructure.Models; public class AgentContextDocument { - public string Instruction { get; set; } - public AgentSourceDocument? Source { get; set; } - public List Steps { get; set; } - public List? Relations { get; set; } + public string? Instruction { get; init; } + public AgentSourceDocument? Source { get; init; } + public List? Steps { get; init; } + public List? Relations { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Models/AgentDocument.cs b/src/MaIN.Infrastructure/Models/AgentDocument.cs index 2ae2ba70..6170b421 100644 --- a/src/MaIN.Infrastructure/Models/AgentDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentDocument.cs @@ -5,15 +5,15 @@ namespace MaIN.Infrastructure.Models; public class AgentDocument { [BsonId] - public string Id { get; set; } - public string Name { get; set; } - public string Model { get; set; } - public string? Description { get; set; } - public bool Started { get; set; } - public AgentContextDocument? Context { get; set; } - public string ChatId { get; set; } = null!; - public int Order { get; set; } - public Dictionary Behaviours { get; set; } - public string CurrentBehaviour { get; set; } - public bool Flow { get; set; } + public required string Id { get; init; } + public required string Name { get; init; } + public required string? Model { get; init; } + public string? Description { get; init; } + public bool Started { get; init; } + public AgentContextDocument? Context { get; init; } + public string? ChatId { get; set; } + public int Order { get; init; } + public Dictionary Behaviours { get; init; } = []; + public string CurrentBehaviour { get; set; } = null!; + public bool Flow { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Models/AgentFlowDocument.cs b/src/MaIN.Infrastructure/Models/AgentFlowDocument.cs index c47a45f0..a77db047 100644 --- a/src/MaIN.Infrastructure/Models/AgentFlowDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentFlowDocument.cs @@ -5,8 +5,8 @@ namespace MaIN.Infrastructure.Models; public class AgentFlowDocument { [BsonId] - public string Id { get; set; } - public string Name { get; set; } - public List Agents { get; set; } - public string Description { get; set; } + public required string Id { get; init; } + public required string Name { get; init; } + public required List Agents { get; init; } + public required string Description { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs b/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs index aef518d7..436185fe 100644 --- a/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs +++ b/src/MaIN.Infrastructure/Models/AgentSourceDocument.cs @@ -2,7 +2,7 @@ namespace MaIN.Models.Rag; public class AgentSourceDocument { - public string? DetailsSerialized { get; set; } - public AgentSourceTypeDocument Type { get; set; } - public string? AdditionalMessage { get; set; } + public string? DetailsSerialized { get; init; } + public AgentSourceTypeDocument Type { get; init; } + public string? AdditionalMessage { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Models/ChatDocument.cs b/src/MaIN.Infrastructure/Models/ChatDocument.cs index 040c0009..9be65937 100644 --- a/src/MaIN.Infrastructure/Models/ChatDocument.cs +++ b/src/MaIN.Infrastructure/Models/ChatDocument.cs @@ -1,4 +1,3 @@ -using MaIN.Domain.Entities; using MongoDB.Bson.Serialization.Attributes; namespace MaIN.Infrastructure.Models; @@ -6,14 +5,14 @@ namespace MaIN.Infrastructure.Models; public class ChatDocument { [BsonId] - public string Id { get; set; } - public string Name { get; set; } - public string Model { get; set; } - public List Messages { get; set; } - public ChatTypeDocument Type { get; set; } - public Dictionary Properties { get; set; } - public bool Visual { get; set; } - public bool Interactive { get; set; } - public bool Translate { get; set; } - public InferenceParamsDocument? InferenceParams { get; set; } + public required string? Id { get; init; } + public required string? Name { get; init; } + public required string? Model { get; init; } + public required List Messages { get; init; } + public ChatTypeDocument Type { get; init; } + public required Dictionary? Properties { get; init; } + public bool Visual { get; init; } + public bool Interactive { get; init; } + public bool Translate { get; init; } + public InferenceParamsDocument? InferenceParams { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Models/MessageDocument.cs b/src/MaIN.Infrastructure/Models/MessageDocument.cs index ab722059..0fd4a43d 100644 --- a/src/MaIN.Infrastructure/Models/MessageDocument.cs +++ b/src/MaIN.Infrastructure/Models/MessageDocument.cs @@ -2,11 +2,11 @@ namespace MaIN.Infrastructure.Models; public class MessageDocument { - public string Role { get; set; } - public string Content { get; set; } - public DateTime Time { get; set; } - public byte[] Images { get; set; } - public string[] Files { get; set; } - public bool Tool { get; set; } - public Dictionary Properties { get; set; } + public required string Role { get; init; } + public required string Content { get; init; } + public DateTime Time { get; init; } + public byte[]? Images { get; init; } + public string[]? Files { get; set; } + public bool Tool { get; init; } + public Dictionary? Properties { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Repositories/Abstract/IAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/Abstract/IAgentFlowRepository.cs index 5836dc07..21e31470 100644 --- a/src/MaIN.Infrastructure/Repositories/Abstract/IAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Abstract/IAgentFlowRepository.cs @@ -5,7 +5,7 @@ namespace MaIN.Infrastructure.Repositories.Abstract; public interface IAgentFlowRepository { Task> GetAllFlows(); - Task GetFlowById(string id); + Task GetFlowById(string id); Task AddFlow(AgentFlowDocument flow); Task UpdateFlow(string id, AgentFlowDocument flow); Task DeleteFlow(string id); diff --git a/src/MaIN.Infrastructure/Repositories/Abstract/IAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/Abstract/IAgentRepository.cs index f6bff715..fb3c1e0b 100644 --- a/src/MaIN.Infrastructure/Repositories/Abstract/IAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Abstract/IAgentRepository.cs @@ -4,10 +4,10 @@ namespace MaIN.Infrastructure.Repositories.Abstract; public interface IAgentRepository { - Task> GetAllAgents(); + Task> GetAllAgents(); Task GetAgentById(string id); - Task AddAgent(AgentDocument? agent); - Task UpdateAgent(string id, AgentDocument? agent); + Task AddAgent(AgentDocument agent); + Task UpdateAgent(string id, AgentDocument agent); Task DeleteAgent(string id); Task Exists(string id); } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Repositories/Abstract/IChatRepository.cs b/src/MaIN.Infrastructure/Repositories/Abstract/IChatRepository.cs index 0b9cd966..1f2460ca 100644 --- a/src/MaIN.Infrastructure/Repositories/Abstract/IChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Abstract/IChatRepository.cs @@ -5,8 +5,8 @@ namespace MaIN.Infrastructure.Repositories.Abstract; public interface IChatRepository { Task> GetAllChats(); - Task GetChatById(string id); + Task GetChatById(string? id); Task AddChat(ChatDocument chat); - Task UpdateChat(string id, ChatDocument chat); - Task DeleteChat(string id); + Task UpdateChat(string? id, ChatDocument chat); + Task DeleteChat(string? id); } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs index abf1919f..ac119f64 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentFlowRepository.cs @@ -11,7 +11,7 @@ public class DefaultAgentFlowRepository : IAgentFlowRepository public async Task> GetAllFlows() => await Task.FromResult(_flows.Values); - public async Task GetFlowById(string id) => + public async Task GetFlowById(string id) => (await Task.FromResult(_flows.GetValueOrDefault(id)))!; public async Task AddFlow(AgentFlowDocument flow) @@ -24,7 +24,7 @@ public async Task AddFlow(AgentFlowDocument flow) public async Task UpdateFlow(string id, AgentFlowDocument flow) { - if (!_flows.TryUpdate(id, flow, _flows.GetValueOrDefault(id))) + if (!_flows.TryUpdate(id, flow, _flows.GetValueOrDefault(id)!)) throw new KeyNotFoundException($"Flow with ID {id} not found."); await Task.CompletedTask; diff --git a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs index df0c3fd0..0993f33f 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultAgentRepository.cs @@ -6,15 +6,15 @@ namespace MaIN.Infrastructure.Repositories; public class DefaultAgentRepository : IAgentRepository { - private readonly ConcurrentDictionary _agents = new(); + private readonly ConcurrentDictionary _agents = new(); - public async Task> GetAllAgents() => + public async Task> GetAllAgents() => await Task.FromResult(_agents.Values); public async Task GetAgentById(string id) => await Task.FromResult(_agents.GetValueOrDefault(id)); - public async Task AddAgent(AgentDocument? agent) + public async Task AddAgent(AgentDocument agent) { if (agent == null) throw new ArgumentNullException(nameof(agent)); @@ -25,9 +25,9 @@ public async Task AddAgent(AgentDocument? agent) await Task.CompletedTask; } - public async Task UpdateAgent(string id, AgentDocument? agent) + public async Task UpdateAgent(string id, AgentDocument agent) { - if (!_agents.TryUpdate(id, agent, _agents.GetValueOrDefault(id))) + if (!_agents.TryUpdate(id, agent, _agents.GetValueOrDefault(id)!)) throw new KeyNotFoundException($"Agent with ID {id} not found."); await Task.CompletedTask; diff --git a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs index 887f238a..5f511cee 100644 --- a/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/DefaultChatRepository.cs @@ -6,13 +6,13 @@ namespace MaIN.Infrastructure.Repositories; public class DefaultChatRepository : IChatRepository { - private readonly ConcurrentDictionary _chats = new(); + private readonly ConcurrentDictionary _chats = new(); public async Task> GetAllChats() => await Task.FromResult(_chats.Values); - public async Task GetChatById(string id) => - await Task.FromResult(_chats.GetValueOrDefault(id)); + public async Task GetChatById(string? id) => + (await Task.FromResult(_chats.GetValueOrDefault(id)))!; public async Task AddChat(ChatDocument chat) { @@ -22,15 +22,15 @@ public async Task AddChat(ChatDocument chat) await Task.CompletedTask; } - public async Task UpdateChat(string id, ChatDocument chat) + public async Task UpdateChat(string? id, ChatDocument chat) { - if (!_chats.TryUpdate(id, chat, _chats.GetValueOrDefault(id))) + if (!_chats.TryUpdate(id, chat, _chats.GetValueOrDefault(id)!)) throw new KeyNotFoundException($"Chat with ID {id} not found."); await Task.CompletedTask; } - public async Task DeleteChat(string id) + public async Task DeleteChat(string? id) { if (!_chats.TryRemove(id, out _)) throw new KeyNotFoundException($"Chat with ID {id} not found."); diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs index ce5d3ecb..956219e4 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentFlowRepository.cs @@ -7,7 +7,7 @@ namespace MaIN.Infrastructure.Repositories.FileSystem; public class FileSystemAgentFlowRepository : IAgentFlowRepository { private readonly string _directoryPath; - private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; + private static readonly JsonSerializerOptions? JsonOptions = new() { WriteIndented = true }; public FileSystemAgentFlowRepository(string basePath) { @@ -32,7 +32,7 @@ public async Task> GetAllFlows() return flows; } - public async Task GetFlowById(string id) + public async Task GetFlowById(string id) { var filePath = GetFilePath(id); if (!File.Exists(filePath)) return null; @@ -47,7 +47,7 @@ public async Task AddFlow(AgentFlowDocument flow) if (File.Exists(filePath)) throw new InvalidOperationException($"Flow with ID {flow.Id} already exists."); - var json = JsonSerializer.Serialize(flow, _jsonOptions); + var json = JsonSerializer.Serialize(flow, JsonOptions); await File.WriteAllTextAsync(filePath, json); } @@ -57,7 +57,7 @@ public async Task UpdateFlow(string id, AgentFlowDocument flow) if (!File.Exists(filePath)) throw new KeyNotFoundException($"Flow with ID {id} not found."); - var json = JsonSerializer.Serialize(flow, _jsonOptions); + var json = JsonSerializer.Serialize(flow, JsonOptions); await File.WriteAllTextAsync(filePath, json); } diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs index 18c4966c..8f674666 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemAgentRepository.cs @@ -7,7 +7,7 @@ namespace MaIN.Infrastructure.Repositories.FileSystem; public class FileSystemAgentRepository : IAgentRepository { private readonly string _directoryPath; - private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; + private static readonly JsonSerializerOptions? JsonOptions = new() { WriteIndented = true }; public FileSystemAgentRepository(string basePath) { @@ -17,7 +17,7 @@ public FileSystemAgentRepository(string basePath) private string GetFilePath(string id) => Path.Combine(_directoryPath, $"{id}.json"); - public async Task> GetAllAgents() + public async Task> GetAllAgents() { var files = Directory.GetFiles(_directoryPath, "*.json"); var agents = new List(); @@ -41,7 +41,7 @@ public FileSystemAgentRepository(string basePath) return JsonSerializer.Deserialize(json); } - public async Task AddAgent(AgentDocument? agent) + public async Task AddAgent(AgentDocument agent) { if (agent == null) throw new ArgumentNullException(nameof(agent)); @@ -50,17 +50,17 @@ public async Task AddAgent(AgentDocument? agent) if (File.Exists(filePath)) throw new InvalidOperationException($"Agent with ID {agent.Id} already exists."); - var json = JsonSerializer.Serialize(agent, _jsonOptions); + var json = JsonSerializer.Serialize(agent, JsonOptions); await File.WriteAllTextAsync(filePath, json); } - public async Task UpdateAgent(string id, AgentDocument? agent) + public async Task UpdateAgent(string id, AgentDocument agent) { var filePath = GetFilePath(id); if (!File.Exists(filePath)) throw new KeyNotFoundException($"Agent with ID {id} not found."); - var json = JsonSerializer.Serialize(agent, _jsonOptions); + var json = JsonSerializer.Serialize(agent, JsonOptions); await File.WriteAllTextAsync(filePath, json); } diff --git a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs index 37327d2b..a565dec4 100644 --- a/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/FileSystem/FileSystemChatRepository.cs @@ -7,7 +7,7 @@ namespace MaIN.Infrastructure.Repositories.FileSystem; public class FileSystemChatRepository : IChatRepository { private readonly string _directoryPath; - private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; + private static readonly JsonSerializerOptions? JsonOptions = new() { WriteIndented = true }; public FileSystemChatRepository(string basePath) { @@ -15,7 +15,7 @@ public FileSystemChatRepository(string basePath) Directory.CreateDirectory(_directoryPath); } - private string GetFilePath(string id) => Path.Combine(_directoryPath, $"{id}.json"); + private string GetFilePath(string? id) => Path.Combine(_directoryPath, $"{id}.json"); public async Task> GetAllChats() { @@ -32,7 +32,7 @@ public async Task> GetAllChats() return chats; } - public async Task GetChatById(string id) + public async Task GetChatById(string? id) { var filePath = GetFilePath(id); if (!File.Exists(filePath)) return null; @@ -47,21 +47,21 @@ public async Task AddChat(ChatDocument chat) if (File.Exists(filePath)) throw new InvalidOperationException($"Chat with ID {chat.Id} already exists."); - var json = JsonSerializer.Serialize(chat, _jsonOptions); + var json = JsonSerializer.Serialize(chat, JsonOptions); await File.WriteAllTextAsync(filePath, json); } - public async Task UpdateChat(string id, ChatDocument chat) + public async Task UpdateChat(string? id, ChatDocument chat) { var filePath = GetFilePath(id); if (!File.Exists(filePath)) throw new KeyNotFoundException($"Chat with ID {id} not found."); - var json = JsonSerializer.Serialize(chat, _jsonOptions); + var json = JsonSerializer.Serialize(chat, JsonOptions); await File.WriteAllTextAsync(filePath, json); } - public async Task DeleteChat(string id) + public async Task DeleteChat(string? id) { var filePath = GetFilePath(id); if (!File.Exists(filePath)) diff --git a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs index 7951d70d..ec9e1772 100644 --- a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentFlowRepository.cs @@ -11,7 +11,7 @@ public class MongoAgentFlowRepository(IMongoDatabase database, string collection public async Task> GetAllFlows() => await _flows.Find(chat => true).ToListAsync(); - public async Task GetFlowById(string id) => + public async Task GetFlowById(string id) => await _flows.Find(flow => flow.Id == id).FirstOrDefaultAsync(); public async Task AddFlow(AgentFlowDocument flow) => diff --git a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs index cf76846e..32a1b8cf 100644 --- a/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Mongo/MongoAgentRepository.cs @@ -6,18 +6,18 @@ namespace MaIN.Infrastructure.Repositories; public class MongoAgentRepository(IMongoDatabase database, string collectionName) : IAgentRepository { - private readonly IMongoCollection _agents = database.GetCollection(collectionName)!; + private readonly IMongoCollection _agents = database.GetCollection(collectionName)!; - public async Task> GetAllAgents() => + public async Task> GetAllAgents() => await _agents.Find(chat => true).ToListAsync(); public async Task GetAgentById(string id) => await _agents!.Find(agent => agent.Id == id).FirstOrDefaultAsync(); - public async Task AddAgent(AgentDocument? agent) => + public async Task AddAgent(AgentDocument agent) => await _agents.InsertOneAsync(agent); - public async Task UpdateAgent(string id, AgentDocument? agent) => + public async Task UpdateAgent(string id, AgentDocument agent) => await _agents.ReplaceOneAsync(x => x.Id == id, agent); public async Task DeleteAgent(string id) => diff --git a/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs b/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs index 0b7001aa..c685679e 100644 --- a/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Mongo/MongoChatRepository.cs @@ -11,15 +11,15 @@ public class MongoChatRepository(IMongoDatabase database, string collectionName) public async Task> GetAllChats() => await _chats.Find(chat => true).ToListAsync(); - public async Task GetChatById(string id) => + public async Task GetChatById(string? id) => await _chats.Find(chat => chat.Id == id).FirstOrDefaultAsync(); public async Task AddChat(ChatDocument chat) => await _chats.InsertOneAsync(chat); - public async Task UpdateChat(string id, ChatDocument chat) => + public async Task UpdateChat(string? id, ChatDocument chat) => await _chats.ReplaceOneAsync(x => x.Id == id, chat); - public async Task DeleteChat(string id) => + public async Task DeleteChat(string? id) => await _chats.DeleteOneAsync(x => x.Id == id); } \ No newline at end of file diff --git a/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentFlowRepository.cs index 0659e572..cab3f379 100644 --- a/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentFlowRepository.cs @@ -50,7 +50,7 @@ public async Task> GetAllFlows() return rows.Select(MapAgentFlowDocument); } - public async Task GetFlowById(string id) + public async Task GetFlowById(string id) { var row = await connection.QueryFirstOrDefaultAsync(@" SELECT * FROM AgentFlows diff --git a/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentRepository.cs index 96dfefc7..34df7c96 100644 --- a/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sql/SqlAgentRepository.cs @@ -9,7 +9,7 @@ namespace MaIN.Infrastructure.Repositories.Sql; public class SqlAgentRepository : IAgentRepository { private readonly IDbConnection _connection; - private readonly JsonSerializerOptions _jsonOptions = new() + private readonly JsonSerializerOptions? _jsonOptions = new() { PropertyNameCaseInsensitive = true }; @@ -65,7 +65,7 @@ private object MapAgentToParameters(AgentDocument agent) }; } - public async Task> GetAllAgents() + public async Task> GetAllAgents() { var rows = await _connection.QueryAsync(@" SELECT * FROM Agents"); @@ -81,7 +81,7 @@ private object MapAgentToParameters(AgentDocument agent) return row != null ? MapAgentDocument(row) : null; } - public async Task AddAgent(AgentDocument? agent) + public async Task AddAgent(AgentDocument agent) { if (agent == null) throw new ArgumentNullException(nameof(agent)); @@ -99,7 +99,7 @@ INSERT INTO Agents ( parameters); } - public async Task UpdateAgent(string id, AgentDocument? agent) + public async Task UpdateAgent(string id, AgentDocument agent) { if (agent == null) throw new ArgumentNullException(nameof(agent)); diff --git a/src/MaIN.Infrastructure/Repositories/Sql/SqlChatRepository.cs b/src/MaIN.Infrastructure/Repositories/Sql/SqlChatRepository.cs index 803d5523..176f758f 100644 --- a/src/MaIN.Infrastructure/Repositories/Sql/SqlChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sql/SqlChatRepository.cs @@ -1,7 +1,6 @@ using System.Data; using System.Text.Json; using Dapper; -using MaIN.Domain.Entities; using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; @@ -9,7 +8,7 @@ namespace MaIN.Infrastructure.Repositories.Sql; public class SqlChatRepository(IDbConnection connection) : IChatRepository { - private readonly JsonSerializerOptions _jsonOptions = new() + private readonly JsonSerializerOptions? _jsonOptions = new() { PropertyNameCaseInsensitive = true }; @@ -66,7 +65,7 @@ public async Task> GetAllChats() return rows.Select(MapChatDocument); } - public async Task GetChatById(string id) + public async Task GetChatById(string? id) { var row = await connection.QueryFirstOrDefaultAsync(@" SELECT * FROM Chats @@ -90,7 +89,7 @@ INSERT INTO Chats ( parameters); } - public async Task UpdateChat(string id, ChatDocument chat) + public async Task UpdateChat(string? id, ChatDocument chat) { if (chat == null) throw new ArgumentNullException(nameof(chat)); @@ -108,7 +107,7 @@ UPDATE Chats parameters); } - public async Task DeleteChat(string id) => + public async Task DeleteChat(string? id) => await connection.ExecuteAsync(@" DELETE FROM Chats WHERE Id = @Id", diff --git a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentFlowRepository.cs b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentFlowRepository.cs index 341472ad..a9821357 100644 --- a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentFlowRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentFlowRepository.cs @@ -50,7 +50,7 @@ public async Task> GetAllFlows() return rows.Select(MapAgentFlowDocument); } - public async Task GetFlowById(string id) + public async Task GetFlowById(string id) { var row = await connection.QueryFirstOrDefaultAsync( "SELECT * FROM AgentFlows WHERE Id = @Id", diff --git a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentRepository.cs b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentRepository.cs index 6f5f2c1b..8a9525f5 100644 --- a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteAgentRepository.cs @@ -8,7 +8,7 @@ namespace MaIN.Infrastructure.Repositories.Sqlite; public class SqliteAgentRepository(IDbConnection connection) : IAgentRepository { - private readonly JsonSerializerOptions _jsonOptions = new() + private readonly JsonSerializerOptions? _jsonOptions = new() { PropertyNameCaseInsensitive = true }; @@ -59,7 +59,7 @@ private object MapAgentToParameters(AgentDocument agent) }; } - public async Task> GetAllAgents() + public async Task> GetAllAgents() { var rows = await connection.QueryAsync( "SELECT * FROM Agents"); @@ -74,7 +74,7 @@ private object MapAgentToParameters(AgentDocument agent) return row != null ? MapAgentDocument(row) : null; } - public async Task AddAgent(AgentDocument? agent) + public async Task AddAgent(AgentDocument agent) { if (agent == null) throw new ArgumentNullException(nameof(agent)); @@ -90,7 +90,7 @@ INSERT INTO Agents ( )", parameters); } - public async Task UpdateAgent(string id, AgentDocument? agent) + public async Task UpdateAgent(string id, AgentDocument agent) { if (agent == null) throw new ArgumentNullException(nameof(agent)); diff --git a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs index 8b41bdc2..625d2e26 100644 --- a/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs +++ b/src/MaIN.Infrastructure/Repositories/Sqlite/SqliteChatRepository.cs @@ -6,7 +6,7 @@ public class SqliteChatRepository(IDbConnection connection) : IChatRepository { - private readonly JsonSerializerOptions _jsonOptions = new() + private readonly JsonSerializerOptions? _jsonOptions = new() { PropertyNameCaseInsensitive = true }; @@ -60,7 +60,7 @@ public async Task> GetAllChats() return rows.Select(MapChatDocument); } - public async Task GetChatById(string id) + public async Task GetChatById(string? id) { var row = await connection.QueryFirstOrDefaultAsync( "SELECT * FROM Chats WHERE Id = @Id", @@ -76,7 +76,7 @@ INSERT INTO Chats (Id, Name, Model, Messages, [Type], Properties, Visual, Infere @Visual, @InferenceParams, @Interactive)", parameters); } - public async Task UpdateChat(string id, ChatDocument chat) + public async Task UpdateChat(string? id, ChatDocument chat) { var parameters = MapChatToParameters(chat); await connection.ExecuteAsync(@" @@ -90,7 +90,7 @@ UPDATE Chats WHERE Id = @Id", parameters); } - public async Task DeleteChat(string id) => + public async Task DeleteChat(string? id) => await connection.ExecuteAsync( "DELETE FROM Chats WHERE Id = @Id", new { Id = id }); diff --git a/src/MaIN.RAG.Demo/Program.cs b/src/MaIN.RAG.Demo/Program.cs index de772433..4a4dcb1f 100644 --- a/src/MaIN.RAG.Demo/Program.cs +++ b/src/MaIN.RAG.Demo/Program.cs @@ -31,15 +31,15 @@ public class Hardware { public int Id { get; set; } - public string Image { get; set; } - public string Name { get; set; } - public string Brand { get; set; } - public string Processor { get; set; } - public string Type { get; set; } - public string Ram { get; set; } - public string Storage { get; set; } - public string Gpu { get; set; } + public string Image { get; set; } = null!; + public string Name { get; set; } = null!; + public string Brand { get; set; } = null!; + public string Processor { get; set; } = null!; + public string Type { get; set; } = null!; + public string Ram { get; set; } = null!; + public string Storage { get; set; } = null!; + public string Gpu { get; set; } = null!; public double Price { get; set; } - public string Availability { get; set; } - public string Description { get; set; } + public string Availability { get; set; } = null!; + public string Description { get; set; } = null!; } \ No newline at end of file diff --git a/src/MaIN.Services/Bootstrapper.cs b/src/MaIN.Services/Bootstrapper.cs index 1654d52b..3b88cea0 100644 --- a/src/MaIN.Services/Bootstrapper.cs +++ b/src/MaIN.Services/Bootstrapper.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Configuration; using MaIN.Infrastructure; -using MaIN.Services.Configuration; using MaIN.Services.Services; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.ImageGenServices; diff --git a/src/MaIN.Services/Mappers/AgentFlowMapper.cs b/src/MaIN.Services/Mappers/AgentFlowMapper.cs index aff3515d..6d93a114 100644 --- a/src/MaIN.Services/Mappers/AgentFlowMapper.cs +++ b/src/MaIN.Services/Mappers/AgentFlowMapper.cs @@ -1,6 +1,5 @@ using MaIN.Domain.Entities.Agents.AgentSource; using MaIN.Infrastructure.Models; -using MaIN.Models.Rag; using MaIN.Services.Models.Rag; namespace MaIN.Services.Mappers; @@ -11,7 +10,7 @@ public static AgentFlowDto ToDto(this AgentFlow agentFlow) { return new AgentFlowDto { - Id = agentFlow.Id, + Id = agentFlow.Id!, Name = agentFlow.Name, Description = agentFlow.Description, Agents = agentFlow.Agents.OrderBy(x => x.Order).Select(x => x.ToDto()).ToList() @@ -39,9 +38,9 @@ public static AgentFlow ToDomain(this AgentFlowDocument agentFlow) => public static AgentFlowDocument ToDocument(this AgentFlow agentFlow) => new() { - Id = agentFlow.Id, + Id = agentFlow.Id!, Name = agentFlow.Name, - Description = agentFlow.Description, + Description = agentFlow.Description!, Agents = agentFlow.Agents.Select(x => x.ToDocument()).ToList() }; diff --git a/src/MaIN.Services/Mappers/AgentMapper.cs b/src/MaIN.Services/Mappers/AgentMapper.cs index 8cf3bad3..cc4c1f19 100644 --- a/src/MaIN.Services/Mappers/AgentMapper.cs +++ b/src/MaIN.Services/Mappers/AgentMapper.cs @@ -10,7 +10,7 @@ namespace MaIN.Services.Mappers; public static class AgentMapper { - public static AgentDto ToDto(this Agent? agent) + public static AgentDto ToDto(this Agent agent) => new() { Id = agent.Id, @@ -20,7 +20,7 @@ public static AgentDto ToDto(this Agent? agent) Started = agent.Started, Flow = agent.Flow, Description = agent.Description, - Behaviours = agent.Behaviours, + Behaviours = agent.Behaviours!, CurrentBehaviour = agent.CurrentBehaviour, Context = agent.Context.ToDto() }; @@ -28,15 +28,15 @@ public static AgentDto ToDto(this Agent? agent) public static AgentContextDto ToDto(this AgentData agentContext) => new() { - Instruction = agentContext.Instruction, + Instruction = agentContext.Instruction!, Relations = agentContext.Relations, - Steps = agentContext?.Steps ?? [], - Source = agentContext?.Source is not null ? new AgentSourceDto() + Steps = agentContext.Steps ?? [], + Source = (agentContext.Source is not null ? new AgentSourceDto() { - Details = agentContext?.Source?.Details, + Details = agentContext.Source?.Details, AdditionalMessage = agentContext?.Source?.AdditionalMessage, - Type = Enum.Parse(agentContext.Source.Type.ToString()) - } : null + Type = Enum.Parse(agentContext?.Source?.Type.ToString()!) + } : null)! }; public static Agent ToDomain(this AgentDto agent) @@ -86,7 +86,7 @@ public static AgentContextDocument ToDocument(this AgentData context) { Instruction = context.Instruction, Relations = context.Relations?.ToList(), - Steps = context.Steps.ToList(), + Steps = context.Steps!.ToList(), Source = context.Source is not null ? new AgentSourceDocument() { DetailsSerialized = JsonSerializer.Serialize(context.Source.Details), @@ -110,7 +110,7 @@ public static AgentDocument ToDocument(this Agent agent) Context = agent.Context.ToDocument() }; - public static Agent ToDomain(this AgentDocument? agent) + public static Agent ToDomain(this AgentDocument agent) => new() { Id = agent.Id, @@ -122,7 +122,7 @@ public static Agent ToDomain(this AgentDocument? agent) Description = agent.Description, Behaviours = agent.Behaviours, CurrentBehaviour = agent.CurrentBehaviour, - Context = agent.Context.ToDomain() + Context = agent.Context!.ToDomain() }; public static AgentData ToDomain(this AgentContextDocument agentContextDocument) diff --git a/src/MaIN.Services/Mappers/ChatMapper.cs b/src/MaIN.Services/Mappers/ChatMapper.cs index 3f757339..ec7fedec 100644 --- a/src/MaIN.Services/Mappers/ChatMapper.cs +++ b/src/MaIN.Services/Mappers/ChatMapper.cs @@ -1,17 +1,14 @@ using MaIN.Domain.Entities; using MaIN.Infrastructure.Models; -using MaIN.Models; using MaIN.Services.Models; -using MaIN.Services.Services; using MaIN.Services.Services.ImageGenServices; -using MongoDB.Bson; using FileInfo = MaIN.Domain.Entities.FileInfo; namespace MaIN.Services.Mappers; public static class ChatMapper { - public static ChatDto ToDto(this Chat? chat) + public static ChatDto ToDto(this Chat chat) => new ChatDto() { Id = chat.Id, @@ -33,19 +30,19 @@ public static MessageDto ToDto(this Message message) Properties = message.Properties, Files = message.Files?.Select(x => new FileInfoDto() { - Content = x.Content, + Content = x.Content ?? string.Empty, Name = x.Name, Extension = x.Extension }) as FileInfoDto[] }; - public static Chat? ToDomain(this ChatDto chat) + public static Chat ToDomain(this ChatDto chat) => new Chat() { Id = chat.Id, Name = chat.Name, Model = chat.Model, - Messages = chat.Messages?.Select(m => m.ToDomain()).ToList(), + Messages = chat.Messages?.Select(m => m.ToDomain()).ToList()!, Visual = chat.Model == ImageGenService.Models.FLUX, Type = Enum.Parse(chat.Type.ToString()), Properties = chat.Properties @@ -76,10 +73,10 @@ public static MessageDocument ToDocument(this Message message) Images = message.Images, Properties = message.Properties, Tool = message.Tool, - Files = message.Files?.Select(x => x.Content).ToArray() ?? [] + Files = (message.Files?.Select(x => x.Content).ToArray() ?? [])! }; - public static ChatDocument ToDocument(this Chat? chat) + public static ChatDocument ToDocument(this Chat chat) => new ChatDocument() { Id = chat.Id, @@ -94,7 +91,7 @@ public static ChatDocument ToDocument(this Chat? chat) Type = Enum.Parse(chat.Type.ToString()) }; - public static Chat? ToDomain(this ChatDocument chat) + public static Chat ToDomain(this ChatDocument chat) => new Chat() { Id = chat.Id, diff --git a/src/MaIN.Services/Models/ChatDto.cs b/src/MaIN.Services/Models/ChatDto.cs index 66563bab..abd1fd2c 100644 --- a/src/MaIN.Services/Models/ChatDto.cs +++ b/src/MaIN.Services/Models/ChatDto.cs @@ -5,21 +5,21 @@ namespace MaIN.Services.Models; public class ChatDto { [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; init; } [JsonPropertyName("model")] - public string Model { get; set; } + public string? Model { get; init; } [JsonPropertyName("messages")] - public List? Messages { get; set; } + public List? Messages { get; init; } [JsonPropertyName("type")] - public ChatTypeDto Type { get; set; } + public ChatTypeDto Type { get; init; } [JsonPropertyName("stream")] public bool Stream { get; set; } = false; [JsonPropertyName("properties")] - public Dictionary Properties { get; set; } + public Dictionary? Properties { get; init; } [JsonPropertyName("visual")] public bool Visual { get; set; } diff --git a/src/MaIN.Services/Models/ChatRequest.cs b/src/MaIN.Services/Models/ChatRequest.cs deleted file mode 100644 index f31f5afd..00000000 --- a/src/MaIN.Services/Models/ChatRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; - -namespace MaIN.Services.Models; - -public class ChatRequest -{ - [JsonPropertyName("model")] - public string Model { get; set; } - [JsonPropertyName("messages")] - public List Messages { get; set; } - [JsonPropertyName("stream")] - public bool Stream { get; set; } = false; -} \ No newline at end of file diff --git a/src/MaIN.Services/Models/ChatResult.cs b/src/MaIN.Services/Models/ChatResult.cs index 04d9ae1a..8552dded 100644 --- a/src/MaIN.Services/Models/ChatResult.cs +++ b/src/MaIN.Services/Models/ChatResult.cs @@ -5,14 +5,14 @@ namespace MaIN.Services.Models; public class ChatResult { [JsonPropertyName("model")] - public string Model { get; set; } + public required string Model { get; init; } [JsonPropertyName("created_at")] public DateTime CreatedAt { get; set; } [JsonPropertyName("message")] - public MessageDto Message { get; set; } + public required MessageDto Message { get; init; } [JsonPropertyName("done")] - public bool Done { get; set; } + public bool Done { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Services/Models/FileInfoDto.cs b/src/MaIN.Services/Models/FileInfoDto.cs index 64f76dbd..f12cb908 100644 --- a/src/MaIN.Services/Models/FileInfoDto.cs +++ b/src/MaIN.Services/Models/FileInfoDto.cs @@ -5,9 +5,9 @@ namespace MaIN.Services.Models; public class FileInfoDto { [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; init; } [JsonPropertyName("extension")] - public string Extension { get; set; } + public string? Extension { get; init; } [JsonPropertyName("content")] - public string Content { get; set; } + public string? Content { get; init; } } diff --git a/src/MaIN.Services/Models/GenerateDto.cs b/src/MaIN.Services/Models/GenerateDto.cs deleted file mode 100644 index 64188ae2..00000000 --- a/src/MaIN.Services/Models/GenerateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace MaIN.Services.Models; - -public class GenerateDto -{ - [JsonPropertyName("model")] public string Model { get; set; } - - [JsonPropertyName("prompt")] public string Prompt { get; set; } = string.Empty; - - [JsonPropertyName("stream")] public bool Stream { get; set; } = false; -} \ No newline at end of file diff --git a/src/MaIN.Services/Models/GenerateResultDto.cs b/src/MaIN.Services/Models/GenerateResultDto.cs deleted file mode 100644 index 462f497b..00000000 --- a/src/MaIN.Services/Models/GenerateResultDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; - -namespace MaIN.Services.Models; - -public class GenerateResultDto -{ - [JsonPropertyName("model")] public string Model { get; set; } - - [JsonPropertyName("created_at")] public string CreatedAt { get; set; } - - [JsonPropertyName("response")] public string Response { get; set; } - - [JsonPropertyName("done")] public bool Done { get; set; } -} \ No newline at end of file diff --git a/src/MaIN.Services/Models/MessageDto.cs b/src/MaIN.Services/Models/MessageDto.cs index 31cd459c..905c5551 100644 --- a/src/MaIN.Services/Models/MessageDto.cs +++ b/src/MaIN.Services/Models/MessageDto.cs @@ -5,16 +5,17 @@ namespace MaIN.Services.Models; public class MessageDto { [JsonPropertyName("role")] - public string Role { get; set; } + public string Role { get; init; } = null!; [JsonPropertyName("content")] - public string Content { get; set; } + public string Content { get; set; } = null!; + [JsonPropertyName("time")] public DateTime Time { get; set; } [JsonPropertyName("images")] - public byte[] Images { get; set; } + public byte[]? Images { get; init; } [JsonPropertyName("files")] - public FileInfoDto[]? Files { get; set; } + public FileInfoDto[]? Files { get; init; } [JsonPropertyName("properties")] - public Dictionary Properties { get; set; } = []; + public Dictionary? Properties { get; set; } = []; } \ No newline at end of file diff --git a/src/MaIN.Services/Models/Ollama/ModelsOllama.cs b/src/MaIN.Services/Models/Ollama/ModelsOllama.cs deleted file mode 100644 index 094b2f76..00000000 --- a/src/MaIN.Services/Models/Ollama/ModelsOllama.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace MaIN.Services.Models.Ollama; - -public class ModelsOllamaResponse -{ - [JsonPropertyName("models")] - public List Models { get; set; } -} - -public class ModelsOllama -{ - [JsonPropertyName("name")] - public string Name { get; set; } -} \ No newline at end of file diff --git a/src/MaIN.Services/Models/Rag/AgentContextDto.cs b/src/MaIN.Services/Models/Rag/AgentContextDto.cs index d523ea3b..7552c358 100644 --- a/src/MaIN.Services/Models/Rag/AgentContextDto.cs +++ b/src/MaIN.Services/Models/Rag/AgentContextDto.cs @@ -6,12 +6,15 @@ namespace MaIN.Services.Models.Rag; public class AgentContextDto { [JsonPropertyName("instruction")] - public string Instruction { get; set; } + public string Instruction { get; init; } = null!; + [JsonPropertyName("source")] - public AgentSourceDto Source { get; set; } + public AgentSourceDto Source { get; init; } = null!; + [JsonPropertyName("steps")] - public List Steps { get; set; } + public List Steps { get; init; } = null!; + [JsonPropertyName("relations")] - public List? Relations { get; set; } + public List? Relations { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Services/Models/Rag/AgentDto.cs b/src/MaIN.Services/Models/Rag/AgentDto.cs index 2cf11edd..74e6a339 100644 --- a/src/MaIN.Services/Models/Rag/AgentDto.cs +++ b/src/MaIN.Services/Models/Rag/AgentDto.cs @@ -5,22 +5,24 @@ namespace MaIN.Services.Models.Rag; public class AgentDto { [JsonPropertyName("id")] - public string Id { get; set; } + public string Id { get; init; } = null!; + [JsonPropertyName("name")] - public string Name { get; set; } + public string Name { get; init; } = null!; + [JsonPropertyName("model")] - public string Model { get; set; } + public string? Model { get; init; } [JsonPropertyName("description")] - public string? Description { get; set; } + public string? Description { get; init; } [JsonPropertyName("started")] - public bool Started { get; set; } + public bool Started { get; init; } [JsonPropertyName("context")] - public AgentContextDto Context { get; set; } + public AgentContextDto Context { get; init; } = null!; [JsonPropertyName("order")] - public int Order { get; set; } + public int Order { get; init; } - [JsonPropertyName("behaviours")] public Dictionary Behaviours { get; set; } = []; - [JsonPropertyName("currentBehaviour")] public string CurrentBehaviour { get; set; } - [JsonPropertyName("flow")] public bool Flow { get; set; } + [JsonPropertyName("behaviours")] public Dictionary Behaviours { get; init; } = []; + [JsonPropertyName("currentBehaviour")] public string CurrentBehaviour { get; init; } = null!; + [JsonPropertyName("flow")] public bool Flow { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Services/Models/Rag/AgentFlowDto.cs b/src/MaIN.Services/Models/Rag/AgentFlowDto.cs index 27a4d525..b64e581f 100644 --- a/src/MaIN.Services/Models/Rag/AgentFlowDto.cs +++ b/src/MaIN.Services/Models/Rag/AgentFlowDto.cs @@ -5,11 +5,10 @@ namespace MaIN.Services.Models.Rag; public class AgentFlowDto { [JsonPropertyName("id")] - public string Id { get; set; } + public required string Id { get; init; } [JsonPropertyName("name")] - public string Name { get; set; } + public required string Name { get; init; } [JsonPropertyName("description")] - public string Description { get; set; } - [JsonPropertyName("agents")] - public List Agents { get; set; } + public string? Description { get; init; } + [JsonPropertyName("agents")] public List Agents { get; init; } = []; } \ No newline at end of file diff --git a/src/MaIN.Services/Models/Rag/AgentRelationDto.cs b/src/MaIN.Services/Models/Rag/AgentRelationDto.cs index ce3a4a24..a234ba7a 100644 --- a/src/MaIN.Services/Models/Rag/AgentRelationDto.cs +++ b/src/MaIN.Services/Models/Rag/AgentRelationDto.cs @@ -5,7 +5,7 @@ namespace MaIN.Services.Models.Rag; public class AgentRelationDto { [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonPropertyName("agentPurpose")] - public string AgentPurpose { get; set; } + public string? AgentPurpose { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Services/Models/Rag/AgentSource/AgentApiSourceDetailsDto.cs b/src/MaIN.Services/Models/Rag/AgentSource/AgentApiSourceDetailsDto.cs index d4b2b832..ad08be6f 100644 --- a/src/MaIN.Services/Models/Rag/AgentSource/AgentApiSourceDetailsDto.cs +++ b/src/MaIN.Services/Models/Rag/AgentSource/AgentApiSourceDetailsDto.cs @@ -4,8 +4,8 @@ namespace MaIN.Services.Models.Rag.AgentSource; public class AgentApiSourceDetailsDto : AgentSourceDetailsBase { - public string Url { get; set; } - public string Method { get; set; } + public required string Url { get; set; } + public required string Method { get; set; } public string? Payload { get; set; } public string? Query { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Services/Models/Rag/AgentSource/AgentFileSourceDetailsDto.cs b/src/MaIN.Services/Models/Rag/AgentSource/AgentFileSourceDetailsDto.cs index cf6dcbba..2280349f 100644 --- a/src/MaIN.Services/Models/Rag/AgentSource/AgentFileSourceDetailsDto.cs +++ b/src/MaIN.Services/Models/Rag/AgentSource/AgentFileSourceDetailsDto.cs @@ -4,5 +4,5 @@ namespace MaIN.Services.Models.Rag.AgentSource; public class AgentFileSourceDetailsDto : AgentSourceDetailsBase { - public string Path { get; set; } + public required string Path { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Services/Services/Abstract/IAgentService.cs b/src/MaIN.Services/Services/Abstract/IAgentService.cs index 02b950fe..cbfa2a1c 100644 --- a/src/MaIN.Services/Services/Abstract/IAgentService.cs +++ b/src/MaIN.Services/Services/Abstract/IAgentService.cs @@ -1,15 +1,14 @@ using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; -using MaIN.Domain.Entities.Agents.AgentSource; namespace MaIN.Services.Services.Abstract; public interface IAgentService { - Task Process(Chat? chat, string agentId, bool translatePrompt = false); + Task Process(Chat chat, string agentId, bool translatePrompt = false); Task CreateAgent(Agent agent, bool flow = false, bool interactiveResponse = false, InferenceParams? inferenceParams = null); - Task GetChatByAgent(string agentId); - Task Restart(string agentId); + Task GetChatByAgent(string agentId); + Task Restart(string agentId); Task> GetAgents(); Task GetAgentById(string id); Task DeleteAgent(string id); diff --git a/src/MaIN.Services/Services/Abstract/IChatService.cs b/src/MaIN.Services/Services/Abstract/IChatService.cs index 16b81595..243a663b 100644 --- a/src/MaIN.Services/Services/Abstract/IChatService.cs +++ b/src/MaIN.Services/Services/Abstract/IChatService.cs @@ -1,15 +1,17 @@ using MaIN.Domain.Entities; -using MaIN.Models; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; namespace MaIN.Services.Services.Abstract; public interface IChatService { - Task Create(Chat? chat); - Task Completions(Chat? chat, bool translatePrompt = false, bool interactiveUpdates = false); - Task Delete(string id); - Task GetById(string id); + Task Create(Chat chat); + Task Completions( + Chat chat, + bool translatePrompt = false, + bool interactiveUpdates = false, + Func? changeOfValue = null); + Task Delete(string? id); + Task GetById(string? id); Task> GetAll(); } \ No newline at end of file diff --git a/src/MaIN.Services/Services/Abstract/IImageGenService.cs b/src/MaIN.Services/Services/Abstract/IImageGenService.cs index 7731a222..b240ab01 100644 --- a/src/MaIN.Services/Services/Abstract/IImageGenService.cs +++ b/src/MaIN.Services/Services/Abstract/IImageGenService.cs @@ -1,11 +1,9 @@ using MaIN.Domain.Entities; -using MaIN.Models; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; namespace MaIN.Services.Services.Abstract; public interface IImageGenService { - Task Send(Chat? chat); + Task Send(Chat chat); } \ No newline at end of file diff --git a/src/MaIN.Services/Services/Abstract/ILLMService.cs b/src/MaIN.Services/Services/Abstract/ILLMService.cs index 5281ede0..0fe06e4e 100644 --- a/src/MaIN.Services/Services/Abstract/ILLMService.cs +++ b/src/MaIN.Services/Services/Abstract/ILLMService.cs @@ -1,17 +1,20 @@ using MaIN.Domain.Entities; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; -namespace MaIN.Services; +namespace MaIN.Services.Services.Abstract; public interface ILLMService { - Task Send(Chat? chat, bool interactiveUpdates = false, bool createSession = false); - Task AskMemory(Chat? chat, + Task Send( + Chat chat, + bool interactiveUpdates = false, + bool createSession = false, + Func? changeOfValue = null); + Task AskMemory(Chat chat, Dictionary? textData = null, Dictionary? fileData = null, List? webUrls = null, List? memory = null); - Task> GetCurrentModels(); - Task CleanSessionCache(string id); + Task> GetCurrentModels(); + Task CleanSessionCache(string? id); } \ No newline at end of file diff --git a/src/MaIN.Services/Services/Abstract/IOllamaService.cs b/src/MaIN.Services/Services/Abstract/IOllamaService.cs index bd9d89d4..e765573f 100644 --- a/src/MaIN.Services/Services/Abstract/IOllamaService.cs +++ b/src/MaIN.Services/Services/Abstract/IOllamaService.cs @@ -1,8 +1,3 @@ -using MaIN.Domain.Entities; -using MaIN.Models; -using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; - namespace MaIN.Services.Services.Abstract; // public interface IOllamaService diff --git a/src/MaIN.Services/Services/Abstract/IStepProcessor.cs b/src/MaIN.Services/Services/Abstract/IStepProcessor.cs index 4f173f04..6ab2508c 100644 --- a/src/MaIN.Services/Services/Abstract/IStepProcessor.cs +++ b/src/MaIN.Services/Services/Abstract/IStepProcessor.cs @@ -6,10 +6,10 @@ namespace MaIN.Services.Services.Abstract; public interface IStepProcessor { - Task ProcessSteps( + Task ProcessSteps( AgentContextDocument context, AgentDocument agent, - Chat? chat, + Chat chat, Func notifyProgress, Func updateChat, ILogger logger); diff --git a/src/MaIN.Services/Services/AgentFlowService.cs b/src/MaIN.Services/Services/AgentFlowService.cs index e1673822..3b89a8c3 100644 --- a/src/MaIN.Services/Services/AgentFlowService.cs +++ b/src/MaIN.Services/Services/AgentFlowService.cs @@ -1,4 +1,5 @@ using MaIN.Domain.Entities.Agents.AgentSource; +using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; using MaIN.Services.Services.Abstract; @@ -28,7 +29,7 @@ public async Task CreateFlow(AgentFlow flow) public async Task DeleteFlow(string id) { var flow = await flowRepository.GetFlowById(id); - foreach (var agent in flow.Agents) + foreach (var agent in flow?.Agents!) { await agentService.DeleteAgent(agent.Id); } diff --git a/src/MaIN.Services/Services/AgentService.cs b/src/MaIN.Services/Services/AgentService.cs index 6d7113e8..a75a7c5a 100644 --- a/src/MaIN.Services/Services/AgentService.cs +++ b/src/MaIN.Services/Services/AgentService.cs @@ -1,6 +1,3 @@ -using System.Text.RegularExpressions; -using Amazon.Runtime.Internal.Transform; -using Amazon.Runtime.Internal.Util; using MaIN.Domain.Entities; using MaIN.Domain.Entities.Agents; using MaIN.Domain.Entities.Agents.Commands; @@ -40,7 +37,7 @@ public AgentService( _llmService = llmService; } - public async Task Process(Chat? chat, string agentId, bool translatePrompt = false) + public async Task Process(Chat chat, string agentId, bool translatePrompt = false) { var agent = await _agentRepository.GetAgentById(agentId); if (agent == null) throw new ArgumentException("Agent not found."); @@ -105,7 +102,7 @@ public async Task CreateAgent(Agent agent, bool flow = false, bool intera agent.Started = true; agent.Flow = flow; agent.Behaviours ??= new Dictionary(); - agent.Behaviours.Add("Default", agent.Context.Instruction); + agent.Behaviours.Add("Default", agent.Context.Instruction!); agent.CurrentBehaviour = "Default"; var agentDocument = agent.ToDocument(); @@ -117,19 +114,19 @@ public async Task CreateAgent(Agent agent, bool flow = false, bool intera return agent; } - public async Task GetChatByAgent(string agentId) + public async Task GetChatByAgent(string agentId) { var agent = await _agentRepository.GetAgentById(agentId); - var chat = await _chatRepository.GetChatById(agent.ChatId); + var chat = await _chatRepository.GetChatById(agent?.ChatId); return chat.ToDomain(); } - public async Task Restart(string agentId) + public async Task Restart(string agentId) { var agent = await _agentRepository.GetAgentById(agentId); var chat = (await _chatRepository.GetChatById(agent?.ChatId!)).ToDomain(); await _llmService.CleanSessionCache(chat.Id); - AgentStateManager.ClearState(agent, chat); + AgentStateManager.ClearState(agent!, chat); await _chatRepository.UpdateChat(chat.Id, chat.ToDocument()); await _agentRepository.UpdateAgent(agent!.Id, agent); diff --git a/src/MaIN.Services/Services/ChatService.cs b/src/MaIN.Services/Services/ChatService.cs index 9a9f249b..903b2ea2 100644 --- a/src/MaIN.Services/Services/ChatService.cs +++ b/src/MaIN.Services/Services/ChatService.cs @@ -1,8 +1,8 @@ using MaIN.Domain.Entities; +using MaIN.Infrastructure.Models; using MaIN.Infrastructure.Repositories.Abstract; using MaIN.Services.Mappers; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.ImageGenServices; @@ -14,13 +14,17 @@ public class ChatService( ILLMService llmService, IImageGenService imageGenService) : IChatService { - public async Task Create(Chat? chat) + public async Task Create(Chat chat) { chat.Type = ChatType.Conversation; await chatProvider.AddChat(chat.ToDocument()); } - public async Task Completions(Chat? chat, bool translate = false, bool interactiveUpdates = false) + public async Task Completions( + Chat chat, + bool translate = false, + bool interactiveUpdates = false, + Func? changeOfValue = null) { if (chat.Model == ImageGenService.Models.FLUX) { @@ -47,7 +51,7 @@ public async Task Completions(Chat? chat, bool translate = false, bo var result = chat.Visual ? await imageGenService.Send(chat) - : await llmService.Send(chat, interactiveUpdates, true); + : await llmService.Send(chat, interactiveUpdates, true, changeOfValue); if (translate) { @@ -68,13 +72,13 @@ public async Task Completions(Chat? chat, bool translate = false, bo return result; } - public async Task Delete(string id) + public async Task Delete(string? id) { await llmService.CleanSessionCache(id); await chatProvider.DeleteChat(id); } - public async Task GetById(string id) + public async Task GetById(string? id) { var chatDocument = await chatProvider.GetChatById(id); return chatDocument.ToDomain(); diff --git a/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs b/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs index f02c00a4..a1d94edb 100644 --- a/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs +++ b/src/MaIN.Services/Services/ImageGenServices/ImageGenDalleService.cs @@ -3,7 +3,6 @@ using MaIN.Domain.Entities; using MaIN.Services.Models; using MaIN.Services.Services.Abstract; -using Microsoft.Extensions.Options; namespace MaIN.Services.Services; @@ -11,18 +10,16 @@ public class OpenAiImageGenService( IHttpClientFactory httpClientFactory, MaINSettings options) : IImageGenService { - public async Task Send(Chat? chat) + public async Task Send(Chat chat) { using var client = httpClientFactory.CreateClient(); client.Timeout = TimeSpan.FromMinutes(5); client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", options.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY")); - var prompt = (chat?.Messages != null - ? chat.Messages - .Select((msg, index) => index == 0 ? msg.Content - : $"&& {msg.Content}") - .Aggregate((current, next) => $"{current} {next}") - : string.Empty)!; + var prompt = (chat.Messages + .Select((msg, index) => index == 0 ? msg.Content + : $"&& {msg.Content}") + .Aggregate((current, next) => $"{current} {next}"))!; var requestBody = new { @@ -35,8 +32,8 @@ public class OpenAiImageGenService( if (!response.IsSuccessStatusCode) { - throw new Exception($"Failed to create image for chat {chat?.Id} with message " + - $"{chat.Messages?.Last().Content}, status code {response.StatusCode}"); + throw new Exception($"Failed to create image for chat {chat.Id} with message " + + $"{chat.Messages.Last().Content}, status code {response.StatusCode}"); } var responseData = await response.Content.ReadFromJsonAsync(); diff --git a/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs b/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs index 566ecb49..4dc9fa31 100644 --- a/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs +++ b/src/MaIN.Services/Services/ImageGenServices/ImageGenService.cs @@ -9,22 +9,20 @@ public class ImageGenService( IHttpClientFactory httpClientFactory, MaINSettings options) : IImageGenService { - public async Task Send(Chat? chat) + public async Task Send(Chat chat) { using var client = httpClientFactory.CreateClient(); client.Timeout = TimeSpan.FromMinutes(5); - var constructedMessage = (chat?.Messages != null - ? chat.Messages - .Select((msg, index) => index == 0 ? msg.Content - : $"&& {msg.Content}") - .Aggregate((current, next) => $"{current} {next}") - : string.Empty)!; + var constructedMessage = (chat.Messages + .Select((msg, index) => index == 0 ? msg.Content + : $"&& {msg.Content}") + .Aggregate((current, next) => $"{current} {next}"))!; var response = await client.PostAsync($"{options.ImageGenUrl}/generate/{constructedMessage}", null); if (!response.IsSuccessStatusCode) { - throw new Exception($"Failed to create completion for chat {chat?.Id} with message " + - $"{chat.Messages?.Last().Content}, status code {response.StatusCode}"); + throw new Exception($"Failed to create completion for chat {chat.Id} with message " + + $"{chat.Messages.Last().Content}, status code {response.StatusCode}"); } byte[] imageBytes = await response.Content.ReadAsByteArrayAsync(); diff --git a/src/MaIN.Services/Services/LLMService/LLMService.cs b/src/MaIN.Services/Services/LLMService/LLMService.cs index 39bbf9dd..255c11f2 100644 --- a/src/MaIN.Services/Services/LLMService/LLMService.cs +++ b/src/MaIN.Services/Services/LLMService/LLMService.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using LLama; -using LLama.Abstractions; using LLama.Common; using LLama.Native; using LLama.Sampling; @@ -14,7 +13,6 @@ using MaIN.Services.Models; using MaIN.Services.Services.Abstract; using MaIN.Services.Utils; -using Microsoft.Extensions.Options; using Microsoft.KernelMemory; using Microsoft.KernelMemory.AI; using Microsoft.KernelMemory.Configuration; @@ -26,11 +24,15 @@ public class LLMService(MaINSettings options, INotificationService notificationS { private const string DefaultModelEnvPath = "MaIN_ModelsPath"; private static readonly ConcurrentDictionary modelCache = new(); - private static readonly ConcurrentDictionary sessionCache = new(); // Cache for chat sessions + private static readonly ConcurrentDictionary sessionCache = new(); // Cache for chat sessions - public async Task Send(Chat? chat, bool interactiveUpdates = false, bool newSession = false) + public async Task Send( + Chat chat, + bool interactiveUpdates = false, + bool newSession = false, + Func? changeOfValue = null) { - if (chat == null || chat.Messages == null || !chat.Messages.Any()) + if (!chat.Messages.Any()) return null; if (chat.Model == KnownModelNames.Llava_7b) //TODO include better support for vision models @@ -38,8 +40,8 @@ public class LLMService(MaINSettings options, INotificationService notificationS return await HandleImageInterpreter(chat)!; } - var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); - var model = KnownModels.GetModel(path, chat.Model); + var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); //TODO add handling for null path + var model = KnownModels.GetModel(path!, chat.Model); var modelKey = model.FileName; // Get or load the model asynchronously. @@ -111,6 +113,7 @@ await notificationService.DispatchNotification( "ReceiveMessageUpdate"); } + changeOfValue?.Invoke(text); resultBuilder.Append(text); } } @@ -138,13 +141,13 @@ await notificationService.DispatchNotification(NotificationMessageBuilder.Create return chatResult; } - private async Task HandleImageInterpreter(Chat? chat) + private async Task HandleImageInterpreter(Chat chat) { - var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); - var modelConfig = KnownModels.GetModel(path, chat.Model); + var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); //TODO add handling for null path + var modelConfig = KnownModels.GetModel(path!, chat.Model); var modelKey = modelConfig.FileName; - var parameters = new ModelParams(Path.Combine(path, modelKey)); + var parameters = new ModelParams(Path.Combine(path!, modelKey)); using var model = LLamaWeights.LoadFromFile(parameters); using var context = model.CreateContext(parameters); @@ -153,7 +156,7 @@ private async Task HandleImageInterpreter(Chat? chat) var inferenceParams = new InferenceParams() { AntiPrompts = new[] { model.Vocab.EOT.ToString() ?? "User:" } }; var ex = new InteractiveExecutor(context); ex.Context.NativeHandle.KvCacheRemove(LLamaSeqId.Zero, -1, -1); - ex.Images.Add(chat.Messages!.Last().Images); + ex.Images.Add(chat.Messages.Last().Images!); var result = new StringBuilder(); await foreach (var text in ex.InferAsync(chat.Messages!.Last().Content, inferenceParams)) { @@ -176,7 +179,7 @@ private async Task HandleImageInterpreter(Chat? chat) return chatResult; } - private ChatSession GetOrCreateSession(string chatId, Func createSession) + private ChatSession GetOrCreateSession(string? chatId, Func createSession) { if (!sessionCache.TryGetValue(chatId, out var session)) { @@ -205,14 +208,14 @@ private void AddMessagesToHistory(ChatSession session, List messages) } [Experimental("SKEXP0001")] - public async Task AskMemory(Chat? chat, + public async Task AskMemory(Chat chat, Dictionary? textData = null, Dictionary? fileData = null, List? webUrls = null, List? memory = null) { - var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); - var model = KnownModels.GetModel(path, chat!.Model); + var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); //TODO add handling for null path + var model = KnownModels.GetModel(path!, chat!.Model); var modelKey = model.FileName; var kernelMemory = CreateMemory(modelKey, path, out var generator); @@ -316,24 +319,24 @@ internal static async Task GetOrLoadModelAsync(string path, string return modelCache.GetOrAdd(modelKey, loadedModel); } - public Task> GetCurrentModels() + public Task> GetCurrentModels() { - var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); - var files = Directory.GetFiles(path, "*.gguf", SearchOption.AllDirectories).ToList(); - var models = new List(); + var path = options.ModelsPath ?? Environment.GetEnvironmentVariable(DefaultModelEnvPath); //TODO add handling for null path + var files = Directory.GetFiles(path!, "*.gguf", SearchOption.AllDirectories).ToList(); + var models = new List(); foreach (var file in files) { - var model = KnownModels.GetModelByFileName(path, Path.GetFileName(file)); + var model = KnownModels.GetModelByFileName(path!, Path.GetFileName(file)); if (model != null) { - models.Add(model.Value.Name); + models.Add(model.Name); } } return Task.FromResult(models); } - public Task CleanSessionCache(string id) + public Task CleanSessionCache(string? id) { sessionCache.Remove(id, out var session); session?.Executor.Context.NativeHandle.KvCacheClear(); @@ -349,9 +352,7 @@ public sealed class LlamaSharpTextGenerator : ITextGenerator, ITextTokenizer, ID { private readonly StatelessExecutor _executor; private readonly LLamaWeights _weights; - private readonly bool _ownsWeights; private readonly LLamaContext _context; - private readonly bool _ownsContext; private readonly InferenceParams? _defaultInferenceParams; public int MaxTokenTotal { get; } @@ -372,10 +373,7 @@ public LlamaSharpTextGenerator( public void Dispose() { - if (this._ownsWeights) - this._weights.Dispose(); - if (!this._ownsContext) - return; + this._weights.Dispose(); this._context.Dispose(); } @@ -392,13 +390,13 @@ private static InferenceParams OptionsToParams( TextGenerationOptions options, InferenceParams? defaultParams) { - if (defaultParams != (InferenceParams)null) + if (defaultParams != null!) return defaultParams with { - AntiPrompts = (IReadOnlyList)defaultParams.AntiPrompts - .Concat((IEnumerable)options.StopSequences).ToList().AsReadOnly(), + AntiPrompts = defaultParams.AntiPrompts + .Concat(options.StopSequences).ToList().AsReadOnly(), MaxTokens = options.MaxTokens ?? defaultParams.MaxTokens, - SamplingPipeline = (ISamplingPipeline)new DefaultSamplingPipeline() + SamplingPipeline = new DefaultSamplingPipeline() { Temperature = (float)options.Temperature, FrequencyPenalty = (float)options.FrequencyPenalty, @@ -449,7 +447,7 @@ public static IKernelMemoryBuilder WithLLamaSharpTextGeneration( [Experimental("KMEXP01")] public static IKernelMemoryBuilder WithLLamaSharpMaINTemp(this IKernelMemoryBuilder builder, - LLamaSharpConfig config, string path, string modelName, out LlamaSharpTextGenerator generator) + LLamaSharpConfig config, string? path, string modelName, out LlamaSharpTextGenerator generator) { // Load the first model with caching. var model = LLMService.GetOrLoadModelAsync(path, modelName).Result; diff --git a/src/MaIN.Services/Services/LLMService/OllamaService.cs b/src/MaIN.Services/Services/LLMService/OllamaService.cs deleted file mode 100644 index 0534e615..00000000 --- a/src/MaIN.Services/Services/LLMService/OllamaService.cs +++ /dev/null @@ -1,58 +0,0 @@ -// using System.Text.Json; -// using MaIN.Domain.Configuration; -// using MaIN.Domain.Entities; -// using MaIN.Services.Configuration; -// using MaIN.Services.Models; -// using MaIN.Services.Models.Ollama; -// using MaIN.Services.Services.Abstract; -// using Microsoft.Extensions.Options; -// -// namespace MaIN.Services.Services; -// -// public class OllamaService( -// IHttpClientFactory httpClientFactory, -// IOptions options) : IOllamaService -// { -// public async Task Send(Chat? chat) -// { -// using var client = httpClientFactory.CreateClient(); -// var response = await client.PostAsync($"{options.Value.OllamaUrl}/api/chat", -// new StringContent(JsonSerializer.Serialize(new ChatRequest() -// { -// Messages = chat.Messages.Select(x => new MessageDto() -// { -// Content = x.Content, -// Role = x.Role, -// Images = x.Images -// }).ToList(), -// Model = chat.Model, -// Stream = chat.Stream, -// }), System.Text.Encoding.UTF8, "application/json")); -// -// if (!response.IsSuccessStatusCode) -// { -// throw new Exception($"Failed to create completion for chat {chat.Id} with message " + -// $"{chat.Messages.Last().Content}, status code {response.StatusCode}"); -// } -// -// var responseBody = await response.Content.ReadAsStringAsync(); -// var result = JsonSerializer.Deserialize(responseBody); -// return result; -// } -// -// public async Task> GetCurrentModels() -// { -// using var client = httpClientFactory.CreateClient(); -// var response = await client.GetAsync($"{options.Value.OllamaUrl}/api/tags"); -// -// if (!response.IsSuccessStatusCode) -// { -// throw new Exception($"Failed to fetch models from Ollama, status code {response.StatusCode}"); -// } -// -// var stringResponse = await response.Content.ReadAsStringAsync(); -// var result = JsonSerializer.Deserialize(stringResponse); -// -// return result!.Models.Select(x => x.Name).ToList(); -// } -// } \ No newline at end of file diff --git a/src/MaIN.Services/Services/LLMService/OpenAiService.cs b/src/MaIN.Services/Services/LLMService/OpenAiService.cs index a6218bbf..e421ecb3 100644 --- a/src/MaIN.Services/Services/LLMService/OpenAiService.cs +++ b/src/MaIN.Services/Services/LLMService/OpenAiService.cs @@ -5,7 +5,6 @@ using MaIN.Services.Models; using MaIN.Services.Services.Abstract; using MaIN.Services.Utils; -using Microsoft.Extensions.Logging; using Microsoft.KernelMemory; namespace MaIN.Services.Services.LLMService; @@ -20,7 +19,6 @@ namespace MaIN.Services.Services.LLMService; using System.Text; using System.Text.Json; using System.Threading.Tasks; -using Microsoft.Extensions.Options; public class OpenAiService( MaINSettings options, @@ -28,11 +26,15 @@ public class OpenAiService( : ILLMService { private readonly HttpClient _httpClient = new(); - private static readonly ConcurrentDictionary> SessionCache = new(); + private static readonly ConcurrentDictionary> SessionCache = new(); - public async Task Send(Chat? chat, bool interactiveUpdates = false, bool createSession = false) + public async Task Send( + Chat chat, + bool interactiveUpdates = false, + bool createSession = false, + Func? changeOfValue = null) { - if (string.IsNullOrEmpty(options.OpenAiKey) || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))) + if (string.IsNullOrEmpty(options.OpenAiKey) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))) { throw new Exception("OpenAI API key is not configured."); } @@ -71,7 +73,7 @@ public class OpenAiService( } else { - if (interactiveUpdates) + if (interactiveUpdates || changeOfValue != null) { // Streaming mode. var requestBody = new @@ -114,6 +116,7 @@ public class OpenAiService( var content = chunk?.Choices?.FirstOrDefault()?.Delta?.Content; if (!string.IsNullOrEmpty(content)) { + changeOfValue?.Invoke(content); resultBuilder.Append(content); await notificationService.DispatchNotification( NotificationMessageBuilder.CreateChatCompletion(chat.Id, content, false), @@ -180,7 +183,7 @@ await notificationService.DispatchNotification( }; } - public async Task AskMemory(Chat? chat, + public async Task AskMemory(Chat chat, Dictionary? textData = null, Dictionary? fileData = null, List? webUrls = null, @@ -250,7 +253,7 @@ await notificationService.DispatchNotification( }; } - public async Task> GetCurrentModels() + public async Task> GetCurrentModels() { var request = new HttpRequestMessage(HttpMethod.Get, "https://api.openai.com/v1/models"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", options.OpenAiKey ?? Environment.GetEnvironmentVariable("OPENAI_API_KEY")); @@ -260,15 +263,15 @@ public async Task> GetCurrentModels() var responseJson = await response.Content.ReadAsStringAsync(); var modelsResponse = JsonSerializer.Deserialize(responseJson); - var models = modelsResponse?.Data? + List models = modelsResponse?.Data? .Where(m => m.Id.StartsWith("gpt-", StringComparison.InvariantCultureIgnoreCase)) .Select(m => m.Id) - .ToList() ?? new List(); + .ToList() ?? new List(); return models; } - public Task CleanSessionCache(string id) + public Task CleanSessionCache(string? id) { SessionCache.TryRemove(id, out _); return Task.CompletedTask; @@ -338,5 +341,5 @@ public class OpenAiModelsResponse public abstract class OpenAiModel { - public string Id { get; set; } + public string? Id { get; set; } } \ No newline at end of file diff --git a/src/MaIN.Services/Services/Models/StepContext.cs b/src/MaIN.Services/Services/Models/StepContext.cs index dc613b7d..57db4040 100644 --- a/src/MaIN.Services/Services/Models/StepContext.cs +++ b/src/MaIN.Services/Services/Models/StepContext.cs @@ -6,7 +6,7 @@ namespace MaIN.Services.Services.Models; public class StepContext { public required AgentDocument Agent { get; init; } - public required Chat? Chat { get; init; } + public required Chat Chat { get; init; } public required Message RedirectMessage { get; init; } public required List TagsToReplaceWithFilter { get; init; } public required string[] Arguments { get; init; } diff --git a/src/MaIN.Services/Services/Models/StepResult.cs b/src/MaIN.Services/Services/Models/StepResult.cs index d92bed50..c8fa1b8c 100644 --- a/src/MaIN.Services/Services/Models/StepResult.cs +++ b/src/MaIN.Services/Services/Models/StepResult.cs @@ -4,6 +4,6 @@ namespace MaIN.Services.Services.Models; public class StepResult { - public Chat? Chat { get; init; } + public Chat Chat { get; init; } public Message? RedirectMessage { get; init; } } \ No newline at end of file diff --git a/src/MaIN.Services/Services/NotificationService.cs b/src/MaIN.Services/Services/NotificationService.cs index 85a72e65..51c89c84 100644 --- a/src/MaIN.Services/Services/NotificationService.cs +++ b/src/MaIN.Services/Services/NotificationService.cs @@ -1,4 +1,3 @@ -using System.Drawing; using System.Text.Json; using MaIN.Services.Services.Abstract; diff --git a/src/MaIN.Services/Services/StepProcessor.cs b/src/MaIN.Services/Services/StepProcessor.cs index 2c097af2..695ea101 100644 --- a/src/MaIN.Services/Services/StepProcessor.cs +++ b/src/MaIN.Services/Services/StepProcessor.cs @@ -23,18 +23,17 @@ public StepProcessor(IEnumerable stepHandlers) } } - public async Task ProcessSteps( + public async Task ProcessSteps( AgentContextDocument context, AgentDocument agent, - Chat? chat, + Chat chat, Func notifyProgress, Func updateChat, ILogger logger) { - Message redirectMessage = chat?.Messages?.Last()!; + Message redirectMessage = chat.Messages.Last()!; var tagsToReplaceWithFilter = new List(); - var index = 0; - foreach (var step in context.Steps) + foreach (var step in context.Steps!) { logger.LogInformation("Processing step: {Step} on agent {agent}", step, agent.Name); @@ -63,7 +62,6 @@ public StepProcessor(IEnumerable stepHandlers) chat = result.Chat; await updateChat(chat!); - index++; } await CleanupBehaviors(agent, tagsToReplaceWithFilter); @@ -84,7 +82,7 @@ private IStepHandler GetStepHandler(string stepName) => private static Task CleanupBehaviors(AgentDocument agent, List tagsToReplaceWithFilter) { - foreach (var key in agent.Behaviours.Keys.ToList()) + foreach (var key in agent.Behaviours!.Keys.ToList()) { agent.Behaviours[key] = tagsToReplaceWithFilter.Aggregate( agent.Behaviours[key], diff --git a/src/MaIN.Services/Services/Steps/BecomeStepHandler.cs b/src/MaIN.Services/Services/Steps/BecomeStepHandler.cs index f9f4468d..13662072 100644 --- a/src/MaIN.Services/Services/Steps/BecomeStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/BecomeStepHandler.cs @@ -1,4 +1,3 @@ -using MaIN.Domain.Entities; using MaIN.Services.Services.Abstract; using MaIN.Services.Services.Models; @@ -19,9 +18,9 @@ public async Task Handle(StepContext context) var newBehaviour = context.Arguments[0]; var messageFilter = context.Agent.Behaviours.GetValueOrDefault(newBehaviour) ?? - context.Agent.Context.Instruction; + context.Agent.Context!.Instruction; - if (context.Chat!.Properties.TryGetValue("data_filter", out var filterQuery)) + if (context.Chat.Properties!.TryGetValue("data_filter", out var filterQuery)) { messageFilter = context.Agent.Behaviours.GetValueOrDefault(newBehaviour)! .Replace("@filter@", filterQuery); diff --git a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs index 6b5af119..00137933 100644 --- a/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs +++ b/src/MaIN.Services/Services/Steps/FechDataStepHandler.cs @@ -30,7 +30,7 @@ public async Task Handle(StepContext context) { Chat = context.Chat, Filter = filterExists ? filter : string.Empty, - Context = context.Agent.Context.ToDomain() + Context = context.Agent.Context!.ToDomain() }; if (fetchCommand.Context.Source!.Type == AgentSourceType.File) @@ -102,7 +102,7 @@ private async Task ProcessJsonResponse(Message response, StepContext context) context.Chat.Messages?.Add(newMessage.ToDomain()); } - private static Chat? GetMemoryChat(StepContext context, string? filterVal) + private static Chat GetMemoryChat(StepContext context, string? filterVal) { var memoryChat = new Chat() { diff --git a/src/MaIN.Services/Steps/Actions.cs b/src/MaIN.Services/Steps/Actions.cs index d5a19bc1..56f9bb2f 100644 --- a/src/MaIN.Services/Steps/Actions.cs +++ b/src/MaIN.Services/Steps/Actions.cs @@ -6,8 +6,6 @@ using MaIN.Domain.Entities.Agents.Commands; using MaIN.Services.Mappers; using MaIN.Services.Models; -using MaIN.Services.Models.Ollama; -using MaIN.Services.Services; using MaIN.Services.Services.Abstract; using MaIN.Services.Utils; using Microsoft.Extensions.DependencyInjection; @@ -18,7 +16,7 @@ namespace MaIN.Services.Steps; public static class Actions { - public static Dictionary Steps { get; private set; } + public static Dictionary Steps { get; private set; } = null!; public static void InitializeAgents(this IServiceProvider serviceProvider) { @@ -30,32 +28,32 @@ public static void InitializeAgents(this IServiceProvider serviceProvider) Steps = new Dictionary { { - "START", new Func>(async startCommand => + "START", new Func>(startCommand => { if (startCommand.Chat?.Visual == true) { - return null; + return Task.FromResult(null); } var message = new Message() { - Content = startCommand.InitialPrompt, + Content = startCommand.InitialPrompt!, Role = "System" }; startCommand.Chat?.Messages?.Add(message); - return new Message() + return Task.FromResult(new Message() { Content = "STARTED", Role = "System", Time = DateTime.UtcNow - }; + })!; }) }, { "REDIRECT", new Func>(async redirectCommand => { var chat = await agentService.GetChatByAgent(redirectCommand.RelatedAgentId); - chat.Messages?.Add(new Message() + chat.Messages.Add(new Message() { Role = "User", Content = redirectCommand.Message.Content, @@ -67,7 +65,7 @@ public static void InitializeAgents(this IServiceProvider serviceProvider) if (!string.IsNullOrEmpty(redirectCommand.Filter)) { - chat.Properties.TryAdd("data_filter", redirectCommand.Filter!); + chat.Properties?.TryAdd("data_filter", redirectCommand.Filter!); } var result = await agentService.Process(chat, redirectCommand.RelatedAgentId); @@ -89,7 +87,7 @@ public static void InitializeAgents(this IServiceProvider serviceProvider) { //TBD var properties = new Dictionary(); - var data = fetchCommand.Context.Source.Type switch + var data = fetchCommand.Context.Source!.Type switch { AgentSourceType.File => await File.ReadAllTextAsync( ((AgentFileSourceDetails)fetchCommand.Context.Source.Details!).Path), @@ -120,7 +118,7 @@ public static void InitializeAgents(this IServiceProvider serviceProvider) "FETCH_DATA*", new Func>(async fetchCommand => { var properties = new Dictionary(); - var data = fetchCommand.Context.Source.Type switch + var data = fetchCommand.Context.Source!.Type switch { AgentSourceType.File => await File.ReadAllTextAsync( JsonSerializer.Deserialize(fetchCommand.Context.Source.Details @@ -170,10 +168,10 @@ public static void InitializeAgents(this IServiceProvider serviceProvider) }; } - private static async Task FetchNoSqlData(object? sourceDetails, string? fetchCommandFilter, + private static Task FetchNoSqlData(object? sourceDetails, string? fetchCommandFilter, Dictionary properties) { - var noSqlDetails = JsonSerializer.Deserialize(sourceDetails.ToString()); + var noSqlDetails = JsonSerializer.Deserialize(sourceDetails!.ToString()!); noSqlDetails!.ConnectionString = noSqlDetails.ConnectionString.Replace("@filter@", fetchCommandFilter); noSqlDetails.Query = noSqlDetails.Query.Replace("@filter@", fetchCommandFilter); noSqlDetails.Collection = noSqlDetails.Collection.Replace("@filter@", fetchCommandFilter); @@ -200,13 +198,13 @@ private static async Task FetchNoSqlData(object? sourceDetails, string? } var jsonResult = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); - return jsonResult; + return Task.FromResult(jsonResult); } private static async Task FetchSqlData(object? sourceDetails, string? fetchCommandFilter, Dictionary properties) { - var sqlDetails = JsonSerializer.Deserialize(sourceDetails.ToString()); + var sqlDetails = JsonSerializer.Deserialize(sourceDetails!.ToString()!); sqlDetails!.ConnectionString = sqlDetails.ConnectionString.Replace("@filter@", fetchCommandFilter); sqlDetails.Query = sqlDetails.Query.Replace("@filter@", fetchCommandFilter); await using SqlConnection connection = new SqlConnection(sqlDetails!.ConnectionString); @@ -239,7 +237,7 @@ private static async Task FetchSqlData(object? sourceDetails, string? fe private static async Task FetchApiData(object? details, string? filter, IHttpClientFactory httpClientFactory, Dictionary properties) { - var apiDetails = JsonSerializer.Deserialize(details.ToString(), new JsonSerializerOptions() + var apiDetails = JsonSerializer.Deserialize(details!.ToString()!, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true, }); diff --git a/src/MaIN.Services/Utils/AgentStateManager.cs b/src/MaIN.Services/Utils/AgentStateManager.cs index 29fffdd8..f76dd497 100644 --- a/src/MaIN.Services/Utils/AgentStateManager.cs +++ b/src/MaIN.Services/Utils/AgentStateManager.cs @@ -1,16 +1,15 @@ using MaIN.Domain.Entities; using MaIN.Infrastructure.Models; -using MaIN.Services.Services; using MaIN.Services.Services.ImageGenServices; namespace MaIN.Services.Utils; public static class AgentStateManager { - public static void ClearState(AgentDocument? agent, Chat? chat) + public static void ClearState(AgentDocument agent, Chat chat) { agent!.CurrentBehaviour = "Default"; - chat.Properties.Clear(); + chat.Properties!.Clear(); if (chat.Model == ImageGenService.Models.FLUX) { @@ -18,7 +17,7 @@ public static void ClearState(AgentDocument? agent, Chat? chat) } else { - chat.Messages![0].Content = agent.Context.Instruction; + chat.Messages![0].Content = agent.Context!.Instruction!; chat.Messages = chat.Messages.Take(1).ToList(); } } diff --git a/src/MaIN.Services/Utils/NotificationMessageBuilder.cs b/src/MaIN.Services/Utils/NotificationMessageBuilder.cs index 11f6617c..dcaebd6f 100644 --- a/src/MaIN.Services/Utils/NotificationMessageBuilder.cs +++ b/src/MaIN.Services/Utils/NotificationMessageBuilder.cs @@ -17,12 +17,12 @@ public static Dictionary CreateActorProgress( }; } - public static Dictionary CreateChatCompletion( - string chatId, - string content, + public static Dictionary CreateChatCompletion( + string? chatId, + string? content, bool done) { - return new Dictionary + return new Dictionary { { "ChatId", chatId }, { "Done", done.ToString() }, diff --git a/src/MaIN/MaIN.csproj b/src/MaIN/MaIN.csproj index cd516618..23e8b66d 100644 --- a/src/MaIN/MaIN.csproj +++ b/src/MaIN/MaIN.csproj @@ -8,7 +8,6 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/MaIN/Program.cs b/src/MaIN/Program.cs index e6846c39..58be41d3 100644 --- a/src/MaIN/Program.cs +++ b/src/MaIN/Program.cs @@ -165,7 +165,7 @@ [FromQuery] bool translate = false, [FromQuery] bool interactiveUpdates = true) => { - var chat = await chatService.Completions(request.ToDomain(), translate, interactiveUpdates); + var chat = await chatService.Completions(request.ToDomain(), translate, interactiveUpdates, null); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonSerializer.Serialize(chat)); }); @@ -180,33 +180,33 @@ }); app.MapDelete("/api/chats/{id}", async (HttpContext context, - [FromServices] IChatService chatService, string id) => + [FromServices] IChatService chatService, string? id) => { await chatService.Delete(id); return Results.NoContent(); }); app.MapGet("/api/chats/{id}", async (HttpContext context, - [FromServices] IChatService chatService, string id) => + [FromServices] IChatService chatService, string? id) => Results.Ok((await chatService.GetById(id)).ToDto())); app.MapGet("/api/chats/models", async (HttpContext context, - [FromServices] ILLMService ollamaService, + [FromServices] ILLMService llmService, [FromServices] IHttpClientFactory httpClientFactory, - [FromServices] IOptions options) => + [FromServices] MaINSettings options) => { - var models = await ollamaService.GetCurrentModels(); + var models = await llmService.GetCurrentModels(); //add flux support var client = httpClientFactory.CreateClient(); try { - var response = await client.GetAsync(options.Value.ImageGenUrl + "/health"); + var response = await client.GetAsync(options.ImageGenUrl + "/health"); if (response.IsSuccessStatusCode) { models.Add(ImageGenService.Models.FLUX); } } - catch (Exception _) + catch (Exception) { Console.WriteLine("No image-gen service running"); } diff --git a/src/MaIN/SignalRNotificationService.cs b/src/MaIN/SignalRNotificationService.cs index 9fc39dbe..8e7c0136 100644 --- a/src/MaIN/SignalRNotificationService.cs +++ b/src/MaIN/SignalRNotificationService.cs @@ -1,5 +1,3 @@ -using System.Drawing; -using System.Text.Json; using MaIN.Services.Services.Abstract; using Microsoft.AspNetCore.SignalR;