diff --git a/JustBigO(Fun)/Controllers/HomeController.cs b/JustBigO(Fun)/Controllers/HomeController.cs index 6effce8..adef60e 100644 --- a/JustBigO(Fun)/Controllers/HomeController.cs +++ b/JustBigO(Fun)/Controllers/HomeController.cs @@ -1,11 +1,12 @@ using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; using JustBigO_Fun_.Data; using JustBigO_Fun_.Models; using JustBigO_Fun_.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using System.Diagnostics; using System.Security.Claims; namespace JustBigO_Fun_.Controllers @@ -145,20 +146,33 @@ public IActionResult Error() [HttpPost] public async Task AnalyzeComplexity( - [FromBody] SubmissionViewModel model, - [FromServices] IComplexityAnalyzer complexityAnalyzer) + [FromBody] SubmissionViewModel model, + [FromServices] IComplexityAnalyzer complexityAnalyzer) { if (string.IsNullOrWhiteSpace(model.SourceCode)) return BadRequest("Codul este gol."); - // DECOMENTĂM ASTA CA SĂ MEARGĂ PE BUNE: - var complexity = await complexityAnalyzer.AnalyzeCodeAsync(model.SourceCode); + try + { + // Gestionăm timeout-ul de 10 secunde să nu blocăm interfața. + var analyzeTask = complexityAnalyzer.AnalyzeCodeAsync(model.SourceCode); + var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10)); - // RETURNĂM REZULTATUL REAL: - return Json(new + if (await Task.WhenAny(analyzeTask, timeoutTask) == analyzeTask) + { + var complexity = await analyzeTask; + return Json(new + { + timeComplexity = complexity.TimeComplexity, + spaceComplexity = complexity.SpaceComplexity + }); + } + + return StatusCode(504, "Timeout: Agentul AI a durat prea mult."); + } + catch (Exception) { - timeComplexity = complexity.TimeComplexity, - spaceComplexity = complexity.SpaceComplexity - }); + return StatusCode(500, "Eroare internă a agentului AI."); + } } [HttpPost] @@ -170,6 +184,9 @@ public async Task GetHint( if (model.ProblemId <= 0) return BadRequest("ProblemId invalid."); + //this is a command to stop the AI enough in order to show the 10s error + //await Task.Delay(15000); + var problem = await _db.Problems.FirstOrDefaultAsync(p => p.Id == model.ProblemId); if (problem == null) return NotFound("Problema nu a fost gasita."); @@ -196,6 +213,7 @@ public async Task CompleteCurrentCode( return BadRequest("Source code is empty."); var lang = string.IsNullOrWhiteSpace(model.Language) ? "python" : model.Language; + var result = await completionService.CompleteAsync( model.ProblemId, model.SourceCode, @@ -289,23 +307,42 @@ public async Task SubmitSolution( { if (!ModelState.IsValid) return BadRequest("Date invalide."); - // 1. Executăm codul prin Docker + // 1. Executăm codul prin Docker (Garantăm rularea neobstrucționată) await codeExecutor.ExecuteAsync(model.ProblemId); // TODO: Aici îți pui logica ta prin care citești dacă testele au trecut - // Momentan simulăm succesul pentru a vedea AI-ul în acțiune pe interfață bool isSuccess = true; string testCasesJson = "[]"; string timeO = "O(?)"; string spaceO = "O(?)"; - // 2. Dacă codul trece testele, apelăm Agentul AI (Acceptance Criteria) + // 2. Dacă codul trece testele, apelăm Agentul AI cu TIMEOUT de 10 secunde if (isSuccess) { - var complexity = await complexityAnalyzer.AnalyzeCodeAsync(model.SourceCode); - timeO = complexity.TimeComplexity; - spaceO = complexity.SpaceComplexity; + try + { + var analyzeTask = complexityAnalyzer.AnalyzeCodeAsync(model.SourceCode); + var timeoutTask = Task.Delay(TimeSpan.FromSeconds(10)); + + if (await Task.WhenAny(analyzeTask, timeoutTask) == analyzeTask) + { + var complexity = await analyzeTask; + timeO = complexity.TimeComplexity; + spaceO = complexity.SpaceComplexity; + } + else + { + // Timeout depășit, AI-ul pică, nu blocăm Docker-ul. + timeO = "Timeout AI"; + spaceO = "Timeout AI"; + } + } + catch + { + timeO = "Eroare AI"; + spaceO = "Eroare AI"; + } } // 3. Returnăm formatul exact pe care îl așteaptă JavaScript-ul diff --git a/JustBigO(Fun)/Migrations/20260528134306_Timer.Designer.cs b/JustBigO(Fun)/Migrations/20260528134306_Timer.Designer.cs new file mode 100644 index 0000000..a3d8681 --- /dev/null +++ b/JustBigO(Fun)/Migrations/20260528134306_Timer.Designer.cs @@ -0,0 +1,460 @@ +// +using System; +using JustBigO_Fun_.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace JustBigO_Fun_.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260528134306_Timer")] + partial class Timer + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("JustBigO_Fun_.Models.Problem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CodeTemplatesJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Difficulty") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("MethodName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.Property("SignatureJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Tags") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Problems"); + }); + + modelBuilder.Entity("JustBigO_Fun_.Models.ProblemTest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExpectedOutputJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InputJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.Property("ProblemId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProblemId"); + + b.ToTable("ProblemTests"); + }); + + modelBuilder.Entity("JustBigO_Fun_.Models.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("ExecutionTimeMs") + .HasColumnType("float"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("MemoryLimitKb") + .HasColumnType("float"); + + b.Property("PeakMemoryKb") + .HasColumnType("float"); + + b.Property("ProblemId") + .HasColumnType("int"); + + b.Property("ResultsJson") + .HasColumnType("nvarchar(max)"); + + b.Property("SourceCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("UserTimeMs") + .HasColumnType("float"); + + b.HasKey("Id"); + + b.HasIndex("ProblemId"); + + b.HasIndex("UserId"); + + b.ToTable("Submissions"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("JustBigO_Fun_.Models.ProblemTest", b => + { + b.HasOne("JustBigO_Fun_.Models.Problem", "Problem") + .WithMany("Tests") + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Problem"); + }); + + modelBuilder.Entity("JustBigO_Fun_.Models.Submission", b => + { + b.HasOne("JustBigO_Fun_.Models.Problem", "Problem") + .WithMany() + .HasForeignKey("ProblemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Problem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("JustBigO_Fun_.Models.Problem", b => + { + b.Navigation("Tests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/JustBigO(Fun)/Migrations/20260528134306_Timer.cs b/JustBigO(Fun)/Migrations/20260528134306_Timer.cs new file mode 100644 index 0000000..5ea3504 --- /dev/null +++ b/JustBigO(Fun)/Migrations/20260528134306_Timer.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace JustBigO_Fun_.Migrations +{ + /// + public partial class Timer : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/JustBigO(Fun)/Views/Home/Solve.cshtml b/JustBigO(Fun)/Views/Home/Solve.cshtml index 6a10dcd..55dffbc 100644 --- a/JustBigO(Fun)/Views/Home/Solve.cshtml +++ b/JustBigO(Fun)/Views/Home/Solve.cshtml @@ -32,15 +32,17 @@

Unlocked only after a successful Submit with status Accepted (Docker) on this page.

+
Code area to refactor

+                  
                     
Optimal data structure / pattern

                     
Refactoring steps (read-only)
@@ -86,11 +88,8 @@ AI is transpiling the code...
-
-
-
@@ -126,6 +125,19 @@ .replace(/"/g, '"'); } + // Functie ajutatoare pentru timeout Frontend (10 secunde) + async function fetchWithTimeout(resource, options = {}) { + const { timeout = 10000 } = options; + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + const response = await fetch(resource, { + ...options, + signal: controller.signal + }); + clearTimeout(id); + return response; + } + // Monaco Editor init require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' } }); require(['vs/editor/editor.main'], function () { @@ -177,16 +189,13 @@ // SignalR Connection const connection = new signalR.HubConnectionBuilder() .withUrl("/translationHub") - .withServerTimeout(300000) // NEW: Wait up to 5 minutes (300,000 ms) + .withServerTimeout(300000) .build(); connection.on("ReceiveCodeChunk", function (chunk) { let currentCode = window.aiEditor.getValue(); let newCode = currentCode + chunk; - - // Scrub out any markdown blocks newCode = newCode.replace(/```[a-zA-Z]*\n?/g, "").replace(/```/g, ""); - window.aiEditor.setValue(newCode); window.aiEditor.revealLine(window.aiEditor.getModel().getLineCount()); }); @@ -198,59 +207,57 @@ const targetSelect = document.getElementById('targetLanguageSelect'); const btnTranspile = document.getElementById('btnTranspile'); - // --- NEW: Update dynamic button text --- function updateTranspileButtonText() { const srcText = sourceSelect.options[sourceSelect.selectedIndex].text; const tgtText = targetSelect.options[targetSelect.selectedIndex].text; btnTranspile.innerHTML = ` Convert from ${srcText} to ${tgtText}`; } - // Set initial text on page load updateTranspileButtonText(); - - // Handle Target Language Change targetSelect.addEventListener('change', updateTranspileButtonText); - // Handle Source Language Change for User Editor sourceSelect.addEventListener('change', function(e) { sourceLang = e.target.value; const template = codeTemplates[sourceLang] || defaultCode; window.editor.setValue(template); monaco.editor.setModelLanguage(window.editor.getModel(), sourceLang); - updateTranspileButtonText(); // Update the button text when source changes + updateTranspileButtonText(); }); // Handle Transpile Button btnTranspile.addEventListener('click', async function() { - const targetLang = targetSelect.value; // <-- NOW PROPERLY READS FROM THE TARGET DROPDOWN + const targetLang = targetSelect.value; const sourceCode = window.editor.getValue(); const loadingBar = document.getElementById('aiLoadingIndicator'); + const aiContainer = document.getElementById('ai-editor-container'); const userContainer = document.getElementById('monaco-editor-container'); - // Reveal AI editor and adjust widths aiContainer.style.display = 'block'; userContainer.classList.remove('w-100'); userContainer.classList.add('w-50'); loadingBar.style.setProperty('display', 'flex', 'important'); - monaco.editor.setModelLanguage(window.aiEditor.getModel(), targetLang); window.aiEditor.setValue(""); try { - await connection.invoke("TranslateCode", sourceCode, sourceLang, targetLang); + // Impunem limită la apelul SignalR + const invokeTask = connection.invoke("TranslateCode", sourceCode, sourceLang, targetLang); + const timeoutTask = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 10000)); + await Promise.race([invokeTask, timeoutTask]); } catch (err) { console.error("Translation Error: ", err); - window.aiEditor.setValue("// Error translating code."); + if (err.message === "Timeout") { + window.aiEditor.setValue("// Timeout: Agentul AI a depășit timpul de așteptare de 10s.\n// Platforma nu a fost blocată. Încearcă din nou mai târziu."); + } else { + window.aiEditor.setValue("// Error translating code."); + } } finally { loadingBar.style.setProperty('display', 'none', 'important'); } }); - - - // Ask for Hint Button Logic document.getElementById('askHintBtn').addEventListener('click', async function() { const hintContainer = document.getElementById('hintContainer'); @@ -263,14 +270,15 @@ hintContainer.textContent = "Generating hint..."; try { - const response = await fetch('/Home/GetHint', { + const response = await fetchWithTimeout('/Home/GetHint', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ problemId: parseInt(problemId), sourceCode: sourceCode, language: language - }) + }), + timeout: 10000 // 10 secunde limită }); if ((response.redirected && response.url.includes('Login')) || response.status === 401) { @@ -279,11 +287,14 @@ } if (!response.ok) throw new Error(await response.text()); - const data = await response.json(); hintContainer.textContent = data.hint || "No hint was returned."; } catch (err) { - hintContainer.textContent = "Error requesting hint: " + err.message; + if (err.name === 'AbortError' || err.message === 'Timeout') { + hintContainer.innerHTML = 'Timpul de așteptare a expirat (10s). Agentul AI este momentan suprasolicitat.'; + } else { + hintContainer.textContent = "Error requesting hint: " + err.message; + } } finally { askHintBtn.disabled = false; } @@ -301,14 +312,15 @@ consoleOutput.innerHTML = '
Analyzing your approach and generating a full solution…
'; try { - const response = await fetch('/Home/CompleteCurrentCode', { + const response = await fetchWithTimeout('/Home/CompleteCurrentCode', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ problemId: parseInt(problemId), sourceCode: sourceCode, language: language - }) + }), + timeout: 10000 }); if ((response.redirected && response.url.includes('Login')) || response.status === 401) { @@ -317,7 +329,7 @@ } if (!response.ok) throw new Error(await response.text()); - + const data = await response.json(); if (window.editor && data.code) { window.editor.setValue(data.code); @@ -332,13 +344,17 @@ if (data.testsPassed) { msg += '
Tests: all passed (local check).
'; } else { - msg += '
Tests: ' + (data.status || 'not all passed') + '. '; + msg += '
Tests: ' + (data.status || 'not all passed') + '.
'; msg += (data.message || 'Review the result or edit the code, then use Submit.') + '
'; } msg += '
'; consoleOutput.innerHTML = msg; } catch (err) { - consoleOutput.innerHTML = '
Complete current code failed: ' + err.message + '
'; + if (err.name === 'AbortError' || err.message === 'Timeout') { + consoleOutput.innerHTML = '
Timeout: Agentul AI a depășit timpul alocat (10s). Platforma nu a fost blocată.
'; + } else { + consoleOutput.innerHTML = '
Complete current code failed: ' + err.message + '
'; + } } finally { btn.disabled = false; } @@ -363,14 +379,15 @@ preSteps.textContent = ''; try { - const response = await fetch('/Home/ImproveSolution', { + const response = await fetchWithTimeout('/Home/ImproveSolution', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ problemId: problemIdSolve, sourceCode: sourceCode, language: language - }) + }), + timeout: 10000 }); if ((response.redirected && response.url.includes('Login')) || response.status === 401) { @@ -393,7 +410,11 @@ } catch (err) { preCode.textContent = ''; preDs.textContent = ''; - preSteps.textContent = 'Error: ' + err.message; + if (err.name === 'AbortError' || err.message === 'Timeout') { + preSteps.textContent = 'Timeout: Agentul AI a depășit timpul de așteptare de 10 secunde.'; + } else { + preSteps.textContent = 'Error: ' + err.message; + } } finally { if (improveUnlockedThisSession) setImproveSolutionEnabled(true); } @@ -412,7 +433,6 @@ improveUnlockedThisSession = false; setImproveSolutionEnabled(false); - // Reset badges const tcBadge = document.getElementById('timeComplexity'); const scBadge = document.getElementById('spaceComplexity'); tcBadge.innerText = "⏱ O(?)"; @@ -427,14 +447,12 @@ body: JSON.stringify({ problemId: parseInt(problemId), sourceCode, language }) }); - // If unauthorized, redirect to login if (response.status === 401 || (response.redirected && response.url.includes('Login'))) { window.location.href = '/Identity/Account/Login?ReturnUrl=' + encodeURIComponent(window.location.pathname); return; } if (!response.ok) throw new Error(await response.text()); - const data = await response.json(); pollStatus(data.submissionId); } catch (err) { @@ -548,14 +566,15 @@ try { const sourceCode = window.editor.getValue(); const problemId = document.getElementById('monaco-editor-container').dataset.problemId; - const aiResponse = await fetch('/Home/AnalyzeComplexity', { + const aiResponse = await fetchWithTimeout('/Home/AnalyzeComplexity', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ problemId: parseInt(problemId), sourceCode: sourceCode, language: document.getElementById('languageSelect').value - }) + }), + timeout: 10000 }); if (aiResponse.ok) { @@ -567,14 +586,21 @@ scBadge.classList.remove('text-warning'); tcBadge.classList.add('text-success', 'fw-bold'); scBadge.classList.add('text-success', 'fw-bold'); + } else { + throw new Error("Bad response status"); } } catch (e) { console.error("Eroare la Agentul AI:", e); - tcBadge.innerText = "⏱ O(?)"; - scBadge.innerText = "💾 O(?)"; + if (e.name === 'AbortError' || e.message === 'Timeout') { + tcBadge.innerText = "⏱ Timeout AI"; + scBadge.innerText = "💾 Timeout AI"; + } else { + tcBadge.innerText = "⏱ Eroare AI"; + scBadge.innerText = "💾 Eroare AI"; + } } } } }); -} +} \ No newline at end of file