diff --git a/EssentialCSharp.Chat.Shared/Services/AIChatService.cs b/EssentialCSharp.Chat.Shared/Services/AIChatService.cs index a3eda26f..3048e753 100644 --- a/EssentialCSharp.Chat.Shared/Services/AIChatService.cs +++ b/EssentialCSharp.Chat.Shared/Services/AIChatService.cs @@ -341,21 +341,17 @@ private static async Task CreateResponseOptionsAsync( string responseText = string.Empty; string responseId = response.Value.Id; - foreach (var outputItem in response.Value.OutputItems) - { #pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - if (outputItem is MessageResponseItem messageItem && - messageItem.Role == MessageRole.Assistant) - { - var textContent = messageItem.Content?.FirstOrDefault()?.Text; - if (!string.IsNullOrEmpty(textContent)) - { - responseText = textContent; - break; - } - } -#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + var assistantMessage = response.Value.OutputItems + .OfType() + .FirstOrDefault(m => m.Role == MessageRole.Assistant && + !string.IsNullOrEmpty(m.Content?.FirstOrDefault()?.Text)); + + if (assistantMessage is not null) + { + responseText = assistantMessage.Content?.FirstOrDefault()?.Text ?? string.Empty; } +#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return (responseText, responseId); } diff --git a/EssentialCSharp.Chat.Shared/Services/ChunkingResultExtensions.cs b/EssentialCSharp.Chat.Shared/Services/ChunkingResultExtensions.cs index a350823a..f6be7d13 100644 --- a/EssentialCSharp.Chat.Shared/Services/ChunkingResultExtensions.cs +++ b/EssentialCSharp.Chat.Shared/Services/ChunkingResultExtensions.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using System.Linq; using EssentialCSharp.Chat.Common.Models; namespace EssentialCSharp.Chat.Common.Services; @@ -8,24 +9,24 @@ public static partial class ChunkingResultExtensions { public static List ToBookContentChunks(this FileChunkingResult result) { - var chunks = new List(); int? chapterNumber = ExtractChapterNumber(result.FileName); - foreach (var chunk in result.Chunks) - { - string chunkText = chunk; - string contentHash = ComputeSha256Hash(chunkText); - - chunks.Add(new BookContentChunk + var chunks = result.Chunks + .Select(chunkText => { - Id = Guid.NewGuid().ToString(), - FileName = result.FileName, - Heading = ExtractHeading(chunkText), - ChunkText = chunkText, - ChapterNumber = chapterNumber, - ContentHash = contentHash - }); - } + var contentHash = ComputeSha256Hash(chunkText); + return new BookContentChunk + { + Id = Guid.NewGuid().ToString(), + FileName = result.FileName, + Heading = ExtractHeading(chunkText), + ChunkText = chunkText, + ChapterNumber = chapterNumber, + ContentHash = contentHash + }; + }) + .ToList(); + return chunks; } diff --git a/EssentialCSharp.Chat/Program.cs b/EssentialCSharp.Chat/Program.cs index 7b3f89f1..ac92ca77 100644 --- a/EssentialCSharp.Chat/Program.cs +++ b/EssentialCSharp.Chat/Program.cs @@ -328,7 +328,7 @@ void WriteChunkingResult(FileChunkingResult result, TextWriter writer) outputDirectory.Create(); foreach (var result in results) { - var outputFile = Path.Combine(outputDirectory.FullName, Path.GetFileNameWithoutExtension(result.FileName) + ".chunks.txt"); + var outputFile = Path.Join(outputDirectory.FullName, Path.GetFileNameWithoutExtension(result.FileName) + ".chunks.txt"); using var writer = new StreamWriter(outputFile, false); WriteChunkingResult(result, writer); Console.WriteLine($"Wrote: {outputFile}"); diff --git a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs index f4a065ea..fbc1d595 100644 --- a/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs +++ b/EssentialCSharp.Web.Tests/ListingSourceCodeServiceTests.cs @@ -133,7 +133,7 @@ private static ListingSourceCodeService CreateService() private static DirectoryInfo GetTestDataPath() { string baseDirectory = AppContext.BaseDirectory; - string testDataPath = Path.Combine(baseDirectory, "TestData"); + string testDataPath = Path.Join(baseDirectory, "TestData"); DirectoryInfo testDataDirectory = new(testDataPath); diff --git a/EssentialCSharp.Web.Tests/WebApplicationFactory.cs b/EssentialCSharp.Web.Tests/WebApplicationFactory.cs index f711629b..07e60731 100644 --- a/EssentialCSharp.Web.Tests/WebApplicationFactory.cs +++ b/EssentialCSharp.Web.Tests/WebApplicationFactory.cs @@ -59,7 +59,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) // Replace IListingSourceCodeService with one backed by TestData services.RemoveAll(); - string testDataPath = Path.Combine(AppContext.BaseDirectory, "TestData"); + string testDataPath = Path.Join(AppContext.BaseDirectory, "TestData"); var fileProvider = new PhysicalFileProvider(testDataPath); services.AddSingleton(sp => { diff --git a/EssentialCSharp.Web/Areas/Identity/Pages/Account/Logout.cshtml.cs b/EssentialCSharp.Web/Areas/Identity/Pages/Account/Logout.cshtml.cs index c869b7fb..fb356e12 100644 --- a/EssentialCSharp.Web/Areas/Identity/Pages/Account/Logout.cshtml.cs +++ b/EssentialCSharp.Web/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -11,15 +11,8 @@ public async Task OnPost(string? returnUrl = null) { await signInManager.SignOutAsync(); logger.LogInformation("User logged out."); - if (returnUrl is not null) - { - return LocalRedirect(returnUrl); - } - else - { // This needs to be a redirect so that the browser performs a new // request and the identity for the user gets updated. - return RedirectToPage(); - } + return returnUrl is not null ? LocalRedirect(returnUrl) : RedirectToPage(); } } diff --git a/EssentialCSharp.Web/Controllers/HomeController.cs b/EssentialCSharp.Web/Controllers/HomeController.cs index 2bc1c463..b3161fd8 100644 --- a/EssentialCSharp.Web/Controllers/HomeController.cs +++ b/EssentialCSharp.Web/Controllers/HomeController.cs @@ -22,7 +22,7 @@ public IActionResult Index() } else if (siteMapping is not null) { - string filePath = Path.Combine(hostingEnvironment.ContentRootPath, Path.Combine(siteMapping.PagePath)); + string filePath = Path.Join(hostingEnvironment.ContentRootPath, Path.Join(siteMapping.PagePath)); HtmlDocument doc = new(); doc.Load(filePath); string headHtml = doc.DocumentNode.Element("html").Element("head").InnerHtml; @@ -74,7 +74,7 @@ public IActionResult Home() public IActionResult Guidelines() { ViewBag.PageTitle = "Coding Guidelines"; - FileInfo fileInfo = new(Path.Combine(hostingEnvironment.ContentRootPath, "Guidelines", "guidelines.json")); + FileInfo fileInfo = new(Path.Join(hostingEnvironment.ContentRootPath, "Guidelines", "guidelines.json")); if (!fileInfo.Exists) { return RedirectToAction(nameof(Error), new { errorMessage = "Guidelines could not be found", statusCode = 404 }); diff --git a/EssentialCSharp.Web/Extensions/SiteMappingListExtensions.cs b/EssentialCSharp.Web/Extensions/SiteMappingListExtensions.cs index 617f5517..3e50d4e5 100644 --- a/EssentialCSharp.Web/Extensions/SiteMappingListExtensions.cs +++ b/EssentialCSharp.Web/Extensions/SiteMappingListExtensions.cs @@ -16,14 +16,9 @@ public static class SiteMappingListExtensions { return siteMappings.FirstOrDefault(); } - foreach (string? potentialMatch in key.GetPotentialMatches()) - { - if (siteMappings.FirstOrDefault(x => x.Keys.Any(x => x == potentialMatch)) is { } siteMap) - { - return siteMap; - } - } - return null; + return key.GetPotentialMatches() + .Select(potentialMatch => siteMappings.FirstOrDefault(x => x.Keys.Any(k => k == potentialMatch))) + .FirstOrDefault(siteMap => siteMap != null); } /// /// Finds percent complete based on a key. diff --git a/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs b/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs index 0032cf4a..1a8abc5b 100644 --- a/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs +++ b/EssentialCSharp.Web/Helpers/SitemapXmlHelpers.cs @@ -23,7 +23,7 @@ public static void GenerateAndSerializeSitemapXml(DirectoryInfo wwwrootDirectory { GenerateSitemapXml(wwwrootDirectory, siteMappings, routeConfigurationService, baseUrl, out List nodes); XmlSerializer sitemapProvider = new(); - var xmlPath = Path.Combine(wwwrootDirectory.FullName, "sitemap.xml"); + var xmlPath = Path.Join(wwwrootDirectory.FullName, "sitemap.xml"); sitemapProvider.Serialize(new SitemapModel(nodes), xmlPath, true); logger.LogInformation("sitemap.xml successfully written to {XmlPath}", xmlPath); } diff --git a/EssentialCSharp.Web/Services/RouteConfigurationService.cs b/EssentialCSharp.Web/Services/RouteConfigurationService.cs index 0429d6b0..c1d75983 100644 --- a/EssentialCSharp.Web/Services/RouteConfigurationService.cs +++ b/EssentialCSharp.Web/Services/RouteConfigurationService.cs @@ -47,14 +47,13 @@ private HashSet ExtractStaticRoutes() // For actions without attribute routes, use conventional routing if (actionDescriptor.AttributeRouteInfo?.Template == null && - actionDescriptor.RouteValues.TryGetValue("action", out var actionName) && - actionDescriptor.RouteValues.TryGetValue("controller", out var controllerName)) + actionDescriptor.RouteValues.TryGetValue("action", out var actionName) && + actionDescriptor.RouteValues.TryGetValue("controller", out var controllerName) && + controllerName?.Equals("Home", StringComparison.OrdinalIgnoreCase) == true && + actionName != null) { - if (controllerName?.Equals("Home", StringComparison.OrdinalIgnoreCase) == true && actionName != null) - { - // Use the action name directly as the route - routes.Add(actionName.ToLowerInvariant()); - } + // Use the action name directly as the route + routes.Add(actionName.ToLowerInvariant()); } } diff --git a/EssentialCSharp.Web/Services/SiteMappingService.cs b/EssentialCSharp.Web/Services/SiteMappingService.cs index c435d14f..a961a0bd 100644 --- a/EssentialCSharp.Web/Services/SiteMappingService.cs +++ b/EssentialCSharp.Web/Services/SiteMappingService.cs @@ -8,7 +8,7 @@ public class SiteMappingService : ISiteMappingService public SiteMappingService(IWebHostEnvironment webHostEnvironment) { - string path = Path.Combine(webHostEnvironment.ContentRootPath, "Chapters", "sitemap.json"); + string path = Path.Join(webHostEnvironment.ContentRootPath, "Chapters", "sitemap.json"); List? siteMappings = System.Text.Json.JsonSerializer.Deserialize>(File.OpenRead(path)) ?? throw new InvalidOperationException("No table of contents found"); SiteMappings = siteMappings; }