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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions JustBigO(Fun).Tests/Controllers/HomeControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public HomeControllerTests()
}

[Fact]
public async Task Index_ReturnsViewWithProblems()
public async Task Problems_ReturnsViewWithProblems()
{
// Arrange
using var db = new ApplicationDbContext(_options);
Expand All @@ -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<ViewResult>(result);
Expand All @@ -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);
Expand All @@ -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<ViewResult>(result);
Expand Down
52 changes: 52 additions & 0 deletions JustBigO(Fun).Tests/Services/GeminiHintGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<HttpMessageHandler>(MockBehavior.Strict);
mockHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(apiResponse)
});

var httpClient = new HttpClient(mockHandler.Object);
var mockConfig = new Mock<IConfiguration>();
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);
}
}
}

19 changes: 13 additions & 6 deletions JustBigO(Fun)/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ public HomeController(ILogger<HomeController> logger, ApplicationDbContext db, I
_markdown = markdown;
}

// --- INCEPUT MODIFICARE ---
// Am adăugat parametrul `difficultyFilter` și am implementat logica de filtrare `.Where(...)`
public async Task<IActionResult> Index(string sortOrder, string difficultyFilter)
public IActionResult Index()
{
return View();
}

public async Task<IActionResult> Problems(string sortOrder, string difficultyFilter)
{
var problemItems = await BuildProblemListAsync(sortOrder, difficultyFilter);
return View(problemItems);
}

private async Task<List<ProblemListItem>> 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";
Expand Down Expand Up @@ -76,7 +85,7 @@ public async Task<IActionResult> 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)
Expand All @@ -89,8 +98,6 @@ private static int GetDifficultyWeight(string difficulty)
_ => 0
};
}
// --- SFARSIT MODIFICARE ---

public async Task<IActionResult> Solve(int? id)
{
Problem? problem;
Expand Down
1 change: 1 addition & 0 deletions JustBigO(Fun)/JustBigO(Fun).csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
<PackageReference Include="HtmlSanitizer.NetCore3.1" Version="1.0.0" />
<PackageReference Include="Markdig" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.11" />
Expand Down
48 changes: 44 additions & 4 deletions JustBigO(Fun)/Services/GeminiHintGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Configuration;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace JustBigO_Fun_.Services;

Expand Down Expand Up @@ -28,9 +29,13 @@ public async Task<string> 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}
Expand Down Expand Up @@ -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<string>(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;
}
}
5 changes: 5 additions & 0 deletions JustBigO(Fun)/Services/HtmlSanitizerService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
96 changes: 3 additions & 93 deletions JustBigO(Fun)/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
@model IEnumerable<JustBigO_Fun_.Models.ProblemListItem>
@{
ViewData["Title"] = "Dashboard";
string GetDifficultyClass(string d) => d?.ToLowerInvariant() switch { "medium" => "medium", "hard" => "hard", _ => "easy" };
ViewData["Title"] = "Home";
}

<section class="jbo-hero">
Expand All @@ -25,7 +23,7 @@
and get instant feedback from smart agents on complexity, transpilations, and hints.
</p>
<div class="jbo-hero-cta">
<a asp-area="" asp-controller="Home" asp-action="Index" asp-fragment="problemset" class="btn jbo-btn-primary">
<a asp-area="" asp-controller="Home" asp-action="Problems" class="btn jbo-btn-primary">
Start solving problems
</a>
<a asp-area="Identity" asp-page="/Account/Login" class="btn jbo-btn-ghost">
Expand Down Expand Up @@ -104,96 +102,8 @@
</div>
</section>

<div class="jbo-grid-row" id="problemset">
<div class="jbo-grid-row" id="agents">
<section class="jbo-section-card">
<header class="jbo-section-header">
<div>
<h2 class="jbo-section-title">
Problemset &amp;
progress
</h2>
<p class="jbo-section-subtitle">
Browse problems by difficulty and track the status of your submissions.
</p>
</div>
<div class="jbo-chip-row">
<a asp-action="Index"
asp-route-sortOrder="@Context.Request.Query["sortOrder"]"
asp-route-difficultyFilter=""
asp-fragment="problemset"
class="jbo-chip"
style="text-decoration: none; color: inherit; @(string.IsNullOrEmpty(ViewData["CurrentFilter"]?.ToString()) ? "outline: 2px solid currentColor; outline-offset: 2px;" : "opacity: 0.6;")">
All
</a>
<a asp-action="Index"
asp-route-sortOrder="@Context.Request.Query["sortOrder"]"
asp-route-difficultyFilter="Easy"
asp-fragment="problemset"
class="jbo-chip jbo-chip--easy"
style="text-decoration: none; @(ViewData["CurrentFilter"]?.ToString() == "Easy" ? "outline: 2px solid currentColor; outline-offset: 2px;" : "opacity: 0.6;")">
Easy
</a>
<a asp-action="Index"
asp-route-sortOrder="@Context.Request.Query["sortOrder"]"
asp-route-difficultyFilter="Medium"
asp-fragment="problemset"
class="jbo-chip jbo-chip--medium"
style="text-decoration: none; @(ViewData["CurrentFilter"]?.ToString() == "Medium" ? "outline: 2px solid currentColor; outline-offset: 2px;" : "opacity: 0.6;")">
Medium
</a>
<a asp-action="Index"
asp-route-sortOrder="@Context.Request.Query["sortOrder"]"
asp-route-difficultyFilter="Hard"
asp-fragment="problemset"
class="jbo-chip jbo-chip--hard"
style="text-decoration: none; @(ViewData["CurrentFilter"]?.ToString() == "Hard" ? "outline: 2px solid currentColor; outline-offset: 2px;" : "opacity: 0.6;")">
Hard
</a>
</div>
</header>

<table class="jbo-mini-table">
<thead>
<tr>
<th>Problem</th>
<th>Tags</th>
<th>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["DifficultySortParm"]"
asp-route-difficultyFilter="@ViewData["CurrentFilter"]"
asp-fragment="problemset"
style="color: inherit; text-decoration: underline; cursor: pointer;">
Difficulty ↕
</a>
</th>
<th>Best status</th>
</tr>
</thead>
<tbody>
@foreach (var p in Model ?? Enumerable.Empty<JustBigO_Fun_.Models.ProblemListItem>())
{

<tr>
<td><a asp-action="Solve" asp-route-id="@p.Id" class="jbo-problem-link">@p.Title</a></td>
<td>@p.Tags</td>
<td><span class="jbo-chip jbo-chip--@GetDifficultyClass(p.Difficulty)">@p.Difficulty</span></td>

<td>
@{
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";
}
<span class="jbo-status-chip @statusClass">@p.BestStatus</span>
</td>
</tr>
}
</tbody>
</table>
</section>

<section class="jbo-section-card" id="agents">
<header class="jbo-section-header">

<div>
Expand Down
Loading
Loading