diff --git a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor
index c83b6d26..12f8b054 100644
--- a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor
+++ b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor
@@ -7,6 +7,13 @@
New chat
-
-
OpenChat Playground
+
diff --git a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor.cs b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor.cs
index 3f755644..d090b341 100644
--- a/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor.cs
+++ b/src/OpenChat.PlaygroundApp/Components/Pages/Chat/ChatHeader.razor.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
+using OpenChat.PlaygroundApp.Configurations;
namespace OpenChat.PlaygroundApp.Components.Pages.Chat;
@@ -6,4 +7,7 @@ public partial class ChatHeader : ComponentBase
{
[Parameter]
public EventCallback OnNewChat { get; set; }
+
+ [Inject]
+ public required AppSettings Settings { get; set; }
}
diff --git a/src/OpenChat.PlaygroundApp/Configurations/AppSettings.cs b/src/OpenChat.PlaygroundApp/Configurations/AppSettings.cs
index a0dfaa81..efc7f507 100644
--- a/src/OpenChat.PlaygroundApp/Configurations/AppSettings.cs
+++ b/src/OpenChat.PlaygroundApp/Configurations/AppSettings.cs
@@ -12,6 +12,11 @@ public partial class AppSettings
///
public ConnectorType ConnectorType { get; set; }
+ ///
+ /// Gets or sets the model name to use.
+ ///
+ public string? Model { get; set; }
+
///
/// Gets or sets the value indicating whether to display help information or not.
///
diff --git a/src/OpenChat.PlaygroundApp/Extensions/AppSettingsExtensions.cs b/src/OpenChat.PlaygroundApp/Extensions/AppSettingsExtensions.cs
new file mode 100644
index 00000000..e5b4828a
--- /dev/null
+++ b/src/OpenChat.PlaygroundApp/Extensions/AppSettingsExtensions.cs
@@ -0,0 +1,58 @@
+using OpenChat.PlaygroundApp.Configurations;
+using OpenChat.PlaygroundApp.Connectors;
+
+namespace OpenChat.PlaygroundApp.Extensions;
+
+///
+/// This represents the extension entity for handling AppSettings configuration.
+///
+public static class AppSettingsExtensions
+{
+ ///
+ /// Configures and adds AppSettings to the service collection with model name populated.
+ ///
+ /// The instance.
+ /// The instance.
+ /// The instance.
+ /// Returns the modified instance.
+ public static IServiceCollection AddAppSettings(this IServiceCollection services, IConfiguration configuration, AppSettings settings)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(configuration);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ ConfigureModelName(configuration, settings);
+ services.AddSingleton(settings);
+
+ return services;
+ }
+
+ ///
+ /// Configures the model name in AppSettings based on the connector type.
+ ///
+ /// The instance.
+ /// The instance.
+ private static void ConfigureModelName(IConfiguration configuration, AppSettings settings)
+ {
+ string? modelFromSettings = settings.ConnectorType switch
+ {
+ ConnectorType.AzureAIFoundry => settings.AzureAIFoundry?.DeploymentName,
+ ConnectorType.FoundryLocal => settings.FoundryLocal?.Alias,
+ ConnectorType.GitHubModels => settings.GitHubModels?.Model,
+ ConnectorType.OpenAI => settings.OpenAI?.Model,
+ ConnectorType.HuggingFace => settings.HuggingFace?.Model,
+ ConnectorType.Anthropic => settings.Anthropic?.Model,
+ ConnectorType.LG => settings.LG?.Model,
+ _ => throw new ArgumentException($"Unsupported ConnectorType: {settings.ConnectorType}")
+ };
+
+ var section = configuration.GetSection(settings.ConnectorType.ToString());
+
+ settings.Model = modelFromSettings ?? settings.ConnectorType switch
+ {
+ ConnectorType.AzureAIFoundry => section.GetValue("DeploymentName"),
+ ConnectorType.FoundryLocal => section.GetValue("Alias"),
+ _ => section.GetValue("Model")
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/OpenChat.PlaygroundApp/Program.cs b/src/OpenChat.PlaygroundApp/Program.cs
index 24a588ca..92ee206b 100644
--- a/src/OpenChat.PlaygroundApp/Program.cs
+++ b/src/OpenChat.PlaygroundApp/Program.cs
@@ -3,6 +3,7 @@
using OpenChat.PlaygroundApp.Abstractions;
using OpenChat.PlaygroundApp.Components;
using OpenChat.PlaygroundApp.Endpoints;
+using OpenChat.PlaygroundApp.Extensions;
using OpenChat.PlaygroundApp.OpenApi;
using OpenChat.PlaygroundApp.Services;
@@ -16,6 +17,8 @@
return;
}
+builder.Services.AddAppSettings(config, settings);
+
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
diff --git a/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs b/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
index a55b4898..18d749d9 100644
--- a/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
+++ b/test/OpenChat.PlaygroundApp.Tests/Components/Pages/Chat/ChatHeaderUITests.cs
@@ -1,6 +1,8 @@
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;
+using OpenChat.PlaygroundApp.Connectors;
+
namespace OpenChat.PlaygroundApp.Tests.Components.Pages.Chat;
public class ChatHeaderUITests : PageTest
@@ -18,12 +20,26 @@ public override async Task InitializeAsync()
public async Task Given_Root_Page_When_Loaded_Then_Header_Should_Be_Visible(string expected)
{
// Act
- var title = await Page.Locator("h1").InnerTextAsync();
+ var title = await Page.Locator("span.app-title-text").InnerTextAsync();
// Assert
title.ShouldBe(expected);
}
+ [Trait("Category", "IntegrationTest")]
+ [Fact]
+ public async Task Given_Root_Page_When_Loaded_Then_Header_Should_Display_ConnectorType_And_Model()
+ {
+ // Act
+ var connector = await Page.Locator("span.app-connector").InnerTextAsync();
+ var model = await Page.Locator("span.app-model").InnerTextAsync();
+
+ // Assert
+ connector.ShouldNotBeNullOrEmpty();
+ Enum.IsDefined(typeof(ConnectorType), connector).ShouldBeTrue();
+ model.ShouldNotBeNullOrEmpty();
+ }
+
[Trait("Category", "IntegrationTest")]
[Fact]
public async Task Given_Root_Page_When_Loaded_Then_NewChat_Button_Should_Be_Visible()
diff --git a/test/OpenChat.PlaygroundApp.Tests/Extensions/AppSettingsExtensionsTests.cs b/test/OpenChat.PlaygroundApp.Tests/Extensions/AppSettingsExtensionsTests.cs
new file mode 100644
index 00000000..7d0ca15a
--- /dev/null
+++ b/test/OpenChat.PlaygroundApp.Tests/Extensions/AppSettingsExtensionsTests.cs
@@ -0,0 +1,340 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+using OpenChat.PlaygroundApp.Configurations;
+using OpenChat.PlaygroundApp.Connectors;
+using OpenChat.PlaygroundApp.Extensions;
+
+namespace OpenChat.PlaygroundApp.Tests.Extensions;
+
+public class AppSettingsExtensionsTests
+{
+ private const string openaiDefaultModel = "openai-default-model";
+ private const string azureDefaultModel = "azure-default-model";
+ private const string foundryDefaultModel = "foundry-default-model";
+ private const string openaiCommandLineModel = "openai-commandline-model";
+ private const string azureCommandLineModel = "azure-commandline-model";
+ private const string foundryCommandLineModel = "foundry-commandline-model";
+
+ private static IConfiguration CreateConfiguration(Dictionary? configData = null)
+ {
+ var data = configData ?? new Dictionary();
+ return new ConfigurationBuilder()
+ .AddInMemoryCollection(data)
+ .Build();
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_Null_Services_When_AddAppSettings_Invoked_Then_It_Should_Throw_ArgumentNullException()
+ {
+ // Arrange
+ IServiceCollection services = null!;
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings { ConnectorType = ConnectorType.OpenAI };
+
+ // Act
+ Action action = () => services.AddAppSettings(configuration, settings);
+
+ // Assert
+ action.ShouldThrow()
+ .Message.ShouldContain("Value cannot be null");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_Null_Configuration_When_AddAppSettings_Invoked_Then_It_Should_Throw_ArgumentNullException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ IConfiguration configuration = null!;
+ var settings = new AppSettings { ConnectorType = ConnectorType.OpenAI };
+
+ // Act
+ Action action = () => services.AddAppSettings(configuration, settings);
+
+ // Assert
+ action.ShouldThrow()
+ .Message.ShouldContain("Value cannot be null");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_Null_Settings_When_AddAppSettings_Invoked_Then_It_Should_Throw_ArgumentNullException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ AppSettings settings = null!;
+
+ // Act
+ Action action = () => services.AddAppSettings(configuration, settings);
+
+ // Assert
+ action.ShouldThrow()
+ .Message.ShouldContain("Value cannot be null");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_Unsupported_ConnectorType_When_AddAppSettings_Invoked_Then_It_Should_Throw_ArgumentException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings { ConnectorType = ConnectorType.Unknown };
+
+ // Act
+ Action action = () => services.AddAppSettings(configuration, settings);
+
+ // Assert
+ action.ShouldThrow()
+ .Message.ShouldContain("Unsupported ConnectorType");
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_Valid_Parameters_When_AddAppSettings_Invoked_Then_It_Should_Return_Services()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings { ConnectorType = ConnectorType.OpenAI };
+
+ // Act
+ var result = services.AddAppSettings(configuration, settings);
+
+ // Assert
+ result.ShouldNotBeNull();
+ result.ShouldBeSameAs(services);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_Valid_Parameters_When_AddAppSettings_Invoked_Then_It_Should_Register_AppSettings_As_Singleton()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings { ConnectorType = ConnectorType.OpenAI };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+ var descriptor = services.FirstOrDefault(sd => sd.ServiceType == typeof(AppSettings));
+
+ // Assert
+ descriptor.ShouldNotBeNull();
+ descriptor.Lifetime.ShouldBe(ServiceLifetime.Singleton);
+ descriptor.ImplementationInstance.ShouldBeSameAs(settings);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_OpenAI_CommandLine_Model_When_AddAppSettings_Invoked_Then_It_Should_Use_CommandLine_Value()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.OpenAI,
+ OpenAI = new OpenAISettings { Model = openaiCommandLineModel }
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(openaiCommandLineModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_AzureAIFoundry_CommandLine_Model_When_AddAppSettings_Invoked_Then_It_Should_Use_CommandLine_Value()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.AzureAIFoundry,
+ AzureAIFoundry = new AzureAIFoundrySettings { DeploymentName = azureCommandLineModel }
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(azureCommandLineModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_FoundryLocal_CommandLine_Model_When_AddAppSettings_Invoked_Then_It_Should_Use_CommandLine_Value()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configuration = CreateConfiguration();
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.FoundryLocal,
+ FoundryLocal = new FoundryLocalSettings { Alias = foundryCommandLineModel }
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(foundryCommandLineModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_OpenAI_No_CommandLine_Model_When_AddAppSettings_Invoked_Then_It_Should_Use_Configuration_Default()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configData = new Dictionary
+ {
+ ["OpenAI:Model"] = openaiDefaultModel
+ };
+ var configuration = CreateConfiguration(configData);
+
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.OpenAI,
+ OpenAI = new OpenAISettings()
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(openaiDefaultModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_AzureAIFoundry_No_CommandLine_Model_When_AddAppSettings_Invoked_Then_It_Should_Use_Configuration_Default()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configData = new Dictionary
+ {
+ ["AzureAIFoundry:DeploymentName"] = azureDefaultModel
+ };
+ var configuration = CreateConfiguration(configData);
+
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.AzureAIFoundry,
+ AzureAIFoundry = new AzureAIFoundrySettings()
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(azureDefaultModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_FoundryLocal_No_CommandLine_Model_When_AddAppSettings_Invoked_Then_It_Should_Use_Configuration_Default()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configData = new Dictionary
+ {
+ ["FoundryLocal:Alias"] = foundryDefaultModel
+ };
+ var configuration = CreateConfiguration(configData);
+
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.FoundryLocal,
+ FoundryLocal = new FoundryLocalSettings()
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(foundryDefaultModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_OpenAI_CommandLine_Priority_When_AddAppSettings_Invoked_Then_It_Should_Override_Configuration()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configData = new Dictionary
+ {
+ ["OpenAI:Model"] = openaiDefaultModel
+ };
+ var configuration = CreateConfiguration(configData);
+
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.OpenAI,
+ OpenAI = new OpenAISettings { Model = openaiCommandLineModel }
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(openaiCommandLineModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_AzureAIFoundry_CommandLine_Priority_When_AddAppSettings_Invoked_Then_It_Should_Override_Configuration()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configData = new Dictionary
+ {
+ ["AzureAIFoundry:DeploymentName"] = azureDefaultModel
+ };
+ var configuration = CreateConfiguration(configData);
+
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.AzureAIFoundry,
+ AzureAIFoundry = new AzureAIFoundrySettings { DeploymentName = azureCommandLineModel }
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(azureCommandLineModel);
+ }
+
+ [Trait("Category", "UnitTest")]
+ [Fact]
+ public void Given_FoundryLocal_CommandLine_Priority_When_AddAppSettings_Invoked_Then_It_Should_Override_Configuration()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var configData = new Dictionary
+ {
+ ["FoundryLocal:Alias"] = foundryDefaultModel
+ };
+ var configuration = CreateConfiguration(configData);
+
+ var settings = new AppSettings
+ {
+ ConnectorType = ConnectorType.FoundryLocal,
+ FoundryLocal = new FoundryLocalSettings { Alias = foundryCommandLineModel }
+ };
+
+ // Act
+ services.AddAppSettings(configuration, settings);
+
+ // Assert
+ settings.Model.ShouldBe(foundryCommandLineModel);
+ }
+}
\ No newline at end of file