diff --git a/JustBigO(Fun).Tests/Controllers/HomeControllerTests.cs b/JustBigO(Fun).Tests/Controllers/HomeControllerTests.cs index 0a42306..c73d868 100644 --- a/JustBigO(Fun).Tests/Controllers/HomeControllerTests.cs +++ b/JustBigO(Fun).Tests/Controllers/HomeControllerTests.cs @@ -25,7 +25,7 @@ public HomeControllerTests() } [Fact] - public async Task Index_ReturnsViewWithProblems() + public async Task Problems_ReturnsViewWithProblems() { // Arrange using var db = new ApplicationDbContext(_options); @@ -44,7 +44,7 @@ public async Task Index_ReturnsViewWithProblems() }; // Act - var result = await controller.Index(null, null); + var result = await controller.Problems(null, null); // Assert var viewResult = Assert.IsType(result); @@ -53,7 +53,7 @@ public async Task Index_ReturnsViewWithProblems() } [Fact] - public async Task Index_FiltersByDifficulty() + public async Task Problems_FiltersByDifficulty() { // Arrange using var db = new ApplicationDbContext(_options); @@ -72,7 +72,7 @@ public async Task Index_FiltersByDifficulty() }; // Act - var result = await controller.Index(null, "Easy"); + var result = await controller.Problems(null, "Easy"); // Assert var viewResult = Assert.IsType(result); diff --git a/JustBigO(Fun).Tests/Services/GeminiHintGeneratorTests.cs b/JustBigO(Fun).Tests/Services/GeminiHintGeneratorTests.cs index 6ed43e9..735aea5 100644 --- a/JustBigO(Fun).Tests/Services/GeminiHintGeneratorTests.cs +++ b/JustBigO(Fun).Tests/Services/GeminiHintGeneratorTests.cs @@ -1,6 +1,9 @@ using Microsoft.Extensions.Configuration; using Moq; +using Moq.Protected; +using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Xunit; using JustBigO_Fun_.Services; @@ -25,5 +28,54 @@ public async Task GenerateHintAsync_ReturnsMissingKeyMessage_WhenApiKeyIsMissing // Assert Assert.Contains("GeminiApiKey is missing", result); } + + [Fact] + public async Task GenerateHintAsync_StripsCodeAndLimitsToTwoSentences() + { + // Arrange + var apiResponse = """ + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "Try tracking the needed counts with a hash map. ```python\nfor x in nums:\n pass\n``` Then move two pointers only when the window is valid. Extra explanation that should be removed." + } + ] + } + } + ] + } + """; + + var mockHandler = new Mock(MockBehavior.Strict); + mockHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(apiResponse) + }); + + var httpClient = new HttpClient(mockHandler.Object); + var mockConfig = new Mock(); + mockConfig.Setup(c => c["GeminiApiKey"]).Returns("fake-key"); + var service = new GeminiHintGenerator(httpClient, mockConfig.Object); + + // Act + var result = await service.GenerateHintAsync("Two Sum", "desc", "code", "python"); + + // Assert + Assert.DoesNotContain("```", result); + Assert.DoesNotContain("pass", result); + Assert.DoesNotContain("Extra explanation", result); + Assert.Equal("Try tracking the needed counts with a hash map. Then move two pointers only when the window is valid.", result); + } } } + diff --git a/JustBigO(Fun)/Controllers/HomeController.cs b/JustBigO(Fun)/Controllers/HomeController.cs index 2fa0712..3d92f70 100644 --- a/JustBigO(Fun)/Controllers/HomeController.cs +++ b/JustBigO(Fun)/Controllers/HomeController.cs @@ -24,9 +24,18 @@ public HomeController(ILogger logger, ApplicationDbContext db, I _markdown = markdown; } - // --- INCEPUT MODIFICARE --- - // Am adăugat parametrul `difficultyFilter` și am implementat logica de filtrare `.Where(...)` - public async Task Index(string sortOrder, string difficultyFilter) + public IActionResult Index() + { + return View(); + } + + public async Task Problems(string sortOrder, string difficultyFilter) + { + var problemItems = await BuildProblemListAsync(sortOrder, difficultyFilter); + return View(problemItems); + } + + private async Task> BuildProblemListAsync(string sortOrder, string difficultyFilter) { // Salvăm parametrii în ViewData pentru a menține starea în butoanele de pe UI ViewData["DifficultySortParm"] = sortOrder == "diff_asc" ? "diff_desc" : "diff_asc"; @@ -76,7 +85,7 @@ public async Task Index(string sortOrder, string difficultyFilter _ => problemItems.OrderBy(p => problems.First(x => x.Id == p.Id).OrderIndex) }; - return View(problemItems.ToList()); + return problemItems.ToList(); } private static int GetDifficultyWeight(string difficulty) @@ -89,8 +98,6 @@ private static int GetDifficultyWeight(string difficulty) _ => 0 }; } - // --- SFARSIT MODIFICARE --- - public async Task Solve(int? id) { Problem? problem; diff --git a/JustBigO(Fun)/JustBigO(Fun).csproj b/JustBigO(Fun)/JustBigO(Fun).csproj index fbfdd4c..906bd03 100644 --- a/JustBigO(Fun)/JustBigO(Fun).csproj +++ b/JustBigO(Fun)/JustBigO(Fun).csproj @@ -25,6 +25,7 @@ + diff --git a/JustBigO(Fun)/Services/GeminiHintGenerator.cs b/JustBigO(Fun)/Services/GeminiHintGenerator.cs index c99db76..f07df4c 100644 --- a/JustBigO(Fun)/Services/GeminiHintGenerator.cs +++ b/JustBigO(Fun)/Services/GeminiHintGenerator.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Configuration; using System.Text; using System.Text.Json; +using System.Text.RegularExpressions; namespace JustBigO_Fun_.Services; @@ -28,9 +29,13 @@ public async Task GenerateHintAsync(string problemTitle, string problemD var prompt = $""" You are a programming mentor for an algorithms platform. - Return exactly one concise hint in English, max 2 short paragraphs. - Do NOT provide full solution code. - Keep the hint actionable and contextual. + Return exactly ONE subtle hint in English, maximum 1-2 short sentences. + STRICT RULES: + - Do NOT provide code, pseudocode, or step-by-step algorithm. + - Do NOT reveal the full strategy or optimal full solution. + - Prefer a nudge: mention one data structure, one invariant, or one leading question. + - If the user is already close, only point to the next tiny step. + - Keep it spoiler-free and concise. Note: The platform uses a Standard IO model (Competitive Programming style). The user must READ from stdin and PRINT to stdout. Problem title: {problemTitle} @@ -86,11 +91,46 @@ Problem statement (HTML possible): return "No valid hint was returned. Please try again."; } - return hintText.Trim(); + return SanitizeHint(hintText); } catch { return "An error occurred while generating the hint. Please try again."; } } + + private static string SanitizeHint(string hintText) + { + if (string.IsNullOrWhiteSpace(hintText)) + return "Try focusing on one small invariant that must stay true at each step."; + + // Remove fenced code blocks or inline code to avoid accidental full solutions. + var sanitized = Regex.Replace(hintText, "```[\\s\\S]*?```", string.Empty); + sanitized = Regex.Replace(sanitized, "`[^`]*`", string.Empty).Trim(); + + // Keep only first 1-2 sentences to force a subtle nudge. + var sentenceMatches = Regex.Matches(sanitized, @"[^.!?]+[.!?]?"); + if (sentenceMatches.Count > 0) + { + var sentenceCount = Math.Min(2, sentenceMatches.Count); + var parts = new List(sentenceCount); + for (var i = 0; i < sentenceCount; i++) + { + var s = sentenceMatches[i].Value.Trim(); + if (!string.IsNullOrWhiteSpace(s)) + parts.Add(s); + } + + sanitized = string.Join(" ", parts).Trim(); + } + + if (string.IsNullOrWhiteSpace(sanitized)) + return "Try focusing on one small invariant that must stay true at each step."; + + // Gentle length cap to reduce chance of spoiler-ish detail. + if (sanitized.Length > 260) + sanitized = sanitized[..260].TrimEnd() + "..."; + + return sanitized; + } } diff --git a/JustBigO(Fun)/Services/HtmlSanitizerService.cs b/JustBigO(Fun)/Services/HtmlSanitizerService.cs index fdfbfb6..21924e2 100644 --- a/JustBigO(Fun)/Services/HtmlSanitizerService.cs +++ b/JustBigO(Fun)/Services/HtmlSanitizerService.cs @@ -1,3 +1,8 @@ +// To fix CS0246, you must add a reference to the Ganss.Xss NuGet package. +// In Visual Studio, right-click your project > Manage NuGet Packages > Browse > search for "Ganss.Xss" > Install. +// Or, run the following command in the Package Manager Console: +// Install-Package Ganss.Xss + using Ganss.Xss; namespace JustBigO_Fun_.Services; diff --git a/JustBigO(Fun)/Views/Home/Index.cshtml b/JustBigO(Fun)/Views/Home/Index.cshtml index 246398f..2d2c963 100644 --- a/JustBigO(Fun)/Views/Home/Index.cshtml +++ b/JustBigO(Fun)/Views/Home/Index.cshtml @@ -1,7 +1,5 @@ -@model IEnumerable @{ - ViewData["Title"] = "Dashboard"; - string GetDifficultyClass(string d) => d?.ToLowerInvariant() switch { "medium" => "medium", "hard" => "hard", _ => "easy" }; + ViewData["Title"] = "Home"; }
@@ -25,7 +23,7 @@ and get instant feedback from smart agents on complexity, transpilations, and hints.

-
+
-
-
-

- Problemset & - progress -

-

- Browse problems by difficulty and track the status of your submissions. -

-
-
-
- - - - - - - - - - - - @foreach (var p in Model ?? Enumerable.Empty()) - { - - - - - - - - - } - -
ProblemTags - - Difficulty ↕ - - Best status
@p.Title@p.Tags@p.Difficulty - @{ - string statusClass = ""; - if (p.BestStatus == "Accepted") statusClass = "jbo-status-chip--accepted"; - else if (p.BestStatus == "TimeLimitExceeded") statusClass = "jbo-status-chip--tle"; - else if (p.BestStatus != "—") statusClass = "jbo-status-chip--wa"; - } - @p.BestStatus -
-
- -
diff --git a/JustBigO(Fun)/Views/Home/Problems.cshtml b/JustBigO(Fun)/Views/Home/Problems.cshtml new file mode 100644 index 0000000..26dc141 --- /dev/null +++ b/JustBigO(Fun)/Views/Home/Problems.cshtml @@ -0,0 +1,83 @@ +@model IEnumerable +@{ + ViewData["Title"] = "Problem Set"; + string GetDifficultyClass(string d) => d?.ToLowerInvariant() switch { "medium" => "medium", "hard" => "hard", _ => "easy" }; +} + +
+
+
+

Problem set

+

+ Browse problems by difficulty and track the status of your submissions. +

+
+ +
+ + + + + + + + + + + + @foreach (var p in Model ?? Enumerable.Empty()) + { + + + + + + + } + +
ProblemTags + + Difficulty ↕ + + Best status
@p.Title@p.Tags@p.Difficulty + @{ + string statusClass = ""; + if (p.BestStatus == "Accepted") statusClass = "jbo-status-chip--accepted"; + else if (p.BestStatus == "TimeLimitExceeded") statusClass = "jbo-status-chip--tle"; + else if (p.BestStatus != "—") statusClass = "jbo-status-chip--wa"; + } + @p.BestStatus +
+
diff --git a/JustBigO(Fun)/Views/Shared/_Layout.cshtml b/JustBigO(Fun)/Views/Shared/_Layout.cshtml index 8a2940f..0a114d9 100644 --- a/JustBigO(Fun)/Views/Shared/_Layout.cshtml +++ b/JustBigO(Fun)/Views/Shared/_Layout.cshtml @@ -27,10 +27,10 @@