diff --git a/.gitea/workflows/mindforge-web.yaml b/.gitea/workflows/mindforge-web.yaml index 6370105..66c2d85 100644 --- a/.gitea/workflows/mindforge-web.yaml +++ b/.gitea/workflows/mindforge-web.yaml @@ -1,4 +1,4 @@ -name: Mindforge Web Build and Deploy +name: Mindforge Web Build and Deploy (internal) on: push: @@ -14,6 +14,7 @@ env: REGISTRY_USERNAME: ivanch IMAGE_WEB: ${{ env.REGISTRY_HOST }}/ivanch/mindforge-web KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} + VITE_API_BASE_URL: http://api.mindforge.haven jobs: build_mindforge_web: @@ -41,7 +42,7 @@ jobs: context: Mindforge.Web platforms: linux/amd64,linux/arm64 build-args: | - VITE_API_BASE_URL=${{ secrets.VITE_API_BASE_URL }} + VITE_API_BASE_URL=${{ env.VITE_API_BASE_URL }} tags: | ${{ env.IMAGE_WEB }}:latest diff --git a/Mindforge.API/.gitignore b/Mindforge.API/.gitignore new file mode 100644 index 0000000..f135ba9 --- /dev/null +++ b/Mindforge.API/.gitignore @@ -0,0 +1,3 @@ +bin +obj +appsettings.Development.json \ No newline at end of file diff --git a/Mindforge.API/Controllers/FileController.cs b/Mindforge.API/Controllers/FileController.cs new file mode 100644 index 0000000..dfa4c92 --- /dev/null +++ b/Mindforge.API/Controllers/FileController.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Mindforge.API.Exceptions; +using Mindforge.API.Models.Requests; +using Mindforge.API.Services.Interfaces; + +namespace Mindforge.API.Controllers +{ + [ApiController] + [Route("api/v1/file")] + public class FileController : ControllerBase + { + private readonly IFileService _fileService; + + public FileController(IFileService fileService) + { + _fileService = fileService; + } + + [HttpPost("check")] + public async Task CheckFile([FromBody] FileCheckRequest request) + { + if (string.IsNullOrWhiteSpace(request.FileContent) || string.IsNullOrWhiteSpace(request.CheckType)) + { + throw new UserException("FileContent and CheckType are required."); + } + + try + { + var base64Bytes = Convert.FromBase64String(request.FileContent); + request.FileContent = System.Text.Encoding.UTF8.GetString(base64Bytes); + } + catch (FormatException) + { + throw new UserException("FileContent must be a valid base64 string."); + } + + var response = await _fileService.CheckFileAsync(request); + return Ok(new { result = response }); + } + } +} diff --git a/Mindforge.API/Controllers/FlashcardController.cs b/Mindforge.API/Controllers/FlashcardController.cs new file mode 100644 index 0000000..8a64b3b --- /dev/null +++ b/Mindforge.API/Controllers/FlashcardController.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Mindforge.API.Exceptions; +using Mindforge.API.Models.Requests; +using Mindforge.API.Services.Interfaces; + +namespace Mindforge.API.Controllers +{ + [ApiController] + [Route("api/v1/flashcard")] + public class FlashcardController : ControllerBase + { + private readonly IFlashcardService _flashcardService; + + public FlashcardController(IFlashcardService flashcardService) + { + _flashcardService = flashcardService; + } + + [HttpPost("generate")] + public async Task Generate([FromBody] FlashcardGenerateRequest request) + { + if (string.IsNullOrWhiteSpace(request.FileContent) || request.Amount <= 0) + { + throw new UserException("FileContent is required and Amount must be greater than 0."); + } + + try + { + var base64Bytes = Convert.FromBase64String(request.FileContent); + request.FileContent = System.Text.Encoding.UTF8.GetString(base64Bytes); + } + catch (FormatException) + { + throw new UserException("FileContent must be a valid base64 string."); + } + + var response = await _flashcardService.GenerateFlashcardsAsync(request); + return Ok(new { result = response }); + } + } +} diff --git a/Mindforge.API/Exceptions/UserException.cs b/Mindforge.API/Exceptions/UserException.cs new file mode 100644 index 0000000..6cb1386 --- /dev/null +++ b/Mindforge.API/Exceptions/UserException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Mindforge.API.Exceptions +{ + public class UserException : Exception + { + public UserException(string message) : base(message) + { + } + } +} diff --git a/Mindforge.API/Middlewares/ExceptionHandlingMiddleware.cs b/Mindforge.API/Middlewares/ExceptionHandlingMiddleware.cs new file mode 100644 index 0000000..7df8ff0 --- /dev/null +++ b/Mindforge.API/Middlewares/ExceptionHandlingMiddleware.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Mindforge.API.Exceptions; + +namespace Mindforge.API.Middlewares +{ + public class ExceptionHandlingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (UserException ex) + { + _logger.LogWarning(ex, "User error"); + await HandleExceptionAsync(context, StatusCodes.Status400BadRequest, ex.Message); + } + catch (Exception ex) + { + _logger.LogError(ex, "Internal server error"); + await HandleExceptionAsync(context, StatusCodes.Status500InternalServerError, $"Internal server error: {ex.Message}"); + } + } + + private static Task HandleExceptionAsync(HttpContext context, int statusCode, string message) + { + context.Response.ContentType = "application/json"; + context.Response.StatusCode = statusCode; + + var result = JsonSerializer.Serialize(new { error = message }); + return context.Response.WriteAsync(result); + } + } +} diff --git a/Mindforge.API/Mindforge.API.csproj b/Mindforge.API/Mindforge.API.csproj new file mode 100644 index 0000000..e292517 --- /dev/null +++ b/Mindforge.API/Mindforge.API.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/Mindforge.API/Mindforge.API.http b/Mindforge.API/Mindforge.API.http new file mode 100644 index 0000000..176f4bb --- /dev/null +++ b/Mindforge.API/Mindforge.API.http @@ -0,0 +1,6 @@ +@Mindforge.API_HostAddress = http://localhost:5123 + +GET {{Mindforge.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Mindforge.API/Models/Enums/LlmProvider.cs b/Mindforge.API/Models/Enums/LlmProvider.cs new file mode 100644 index 0000000..83eb027 --- /dev/null +++ b/Mindforge.API/Models/Enums/LlmProvider.cs @@ -0,0 +1,8 @@ +namespace Mindforge.API.Models.Enums +{ + public enum LlmProvider + { + OpenAI, + Gemini + } +} diff --git a/Mindforge.API/Models/Requests/FileCheckRequest.cs b/Mindforge.API/Models/Requests/FileCheckRequest.cs new file mode 100644 index 0000000..58c13dd --- /dev/null +++ b/Mindforge.API/Models/Requests/FileCheckRequest.cs @@ -0,0 +1,13 @@ +namespace Mindforge.API.Models.Requests +{ + public class FileCheckRequest + { + public string FileContent { get; set; } = string.Empty; + public string FileName { get; set; } = string.Empty; + + /// + /// Expected values: "language" or "content" + /// + public string CheckType { get; set; } = string.Empty; + } +} diff --git a/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs b/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs new file mode 100644 index 0000000..216cab0 --- /dev/null +++ b/Mindforge.API/Models/Requests/FlashcardGenerateRequest.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace Mindforge.API.Models.Requests +{ + public class FlashcardGenerateRequest + { + public string FileContent { get; set; } = string.Empty; + public string FileName { get; set; } = string.Empty; + public int Amount { get; set; } + [JsonConverter(typeof(JsonStringEnumConverter))] + public FlashcardMode? Mode { get; set; } = FlashcardMode.Simple; + } + + public enum FlashcardMode + { + Basic, + Simple, + Detailed, + Hyper + } +} diff --git a/Mindforge.API/Program.cs b/Mindforge.API/Program.cs new file mode 100644 index 0000000..0ea2199 --- /dev/null +++ b/Mindforge.API/Program.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Mindforge.API.Providers; +using Mindforge.API.Services; +using Mindforge.API.Services.Interfaces; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +builder.Logging.AddConsole(); +builder.Logging.AddDebug(); + +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +// Configure CORS +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAll", policy => + { + policy.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); +}); + +// Register HttpClient for providers +builder.Services.AddHttpClient(); + +// Register Providers +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Register Services +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +app.UseCors("AllowAll"); + +app.UseMiddleware(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +// Check for env vars +var openAiKey = builder.Configuration["OPENAI_API_KEY"]; +var geminiKey = builder.Configuration["GEMINI_API_KEY"]; + +if (string.IsNullOrEmpty(openAiKey)) +{ + app.Logger.LogWarning("OPENAI_API_KEY not found in configuration."); +} + +if (string.IsNullOrEmpty(geminiKey)) +{ + app.Logger.LogWarning("GEMINI_API_KEY not found in configuration."); +} + +app.Run(); diff --git a/Mindforge.API/Properties/launchSettings.json b/Mindforge.API/Properties/launchSettings.json new file mode 100644 index 0000000..0b3a1fe --- /dev/null +++ b/Mindforge.API/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5123", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7116;http://localhost:5123", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Mindforge.API/Providers/GeminiApiProvider.cs b/Mindforge.API/Providers/GeminiApiProvider.cs new file mode 100644 index 0000000..d57f9bf --- /dev/null +++ b/Mindforge.API/Providers/GeminiApiProvider.cs @@ -0,0 +1,202 @@ +using System; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + +namespace Mindforge.API.Providers +{ + public class GeminiApiProvider : ILlmApiProvider + { + private readonly HttpClient _httpClient; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public GeminiApiProvider(HttpClient httpClient, IConfiguration configuration, ILogger logger) + { + _httpClient = httpClient; + _httpClient.Timeout = TimeSpan.FromMinutes(5); + _configuration = configuration; + _logger = logger; + } + + public async Task SendRequestAsync(string systemPrompt, string userPrompt, string model) + { + var apiKey = _configuration["GEMINI_API_KEY"]; + if (string.IsNullOrEmpty(apiKey)) + { + throw new Exception("GEMINI_API_KEY not found in configuration."); + } + + var apiBase = "https://generativelanguage.googleapis.com/v1beta"; + var url = $"{apiBase.TrimEnd('/')}/models/{model}:generateContent?key={apiKey}"; + + var reqBody = new + { + system_instruction = string.IsNullOrEmpty(systemPrompt) ? null : new + { + parts = new[] { new { text = systemPrompt } } + }, + contents = new[] + { + new + { + role = "user", + parts = new[] { new { text = userPrompt } } + } + } + }; + + var jsonBody = JsonSerializer.Serialize(reqBody, new JsonSerializerOptions { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }); + + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new Exception($"Gemini API error status {(int)response.StatusCode}: {responseBody}"); + } + + var result = JsonSerializer.Deserialize(responseBody); + if (result.TryGetProperty("candidates", out var candidates) && candidates.GetArrayLength() > 0) + { + var content = candidates[0].GetProperty("content"); + if (content.TryGetProperty("parts", out var parts) && parts.GetArrayLength() > 0) + { + return parts[0].GetProperty("text").GetString() ?? string.Empty; + } + } + + throw new Exception("empty response from Gemini API"); + } + + public async Task SendRequestBatchAsync(string systemPrompt, string userPrompt, string model) + { + var apiKey = _configuration["GEMINI_API_KEY"]; + if (string.IsNullOrEmpty(apiKey)) + throw new Exception("GEMINI_API_KEY not found in configuration."); + + var apiBase = "https://generativelanguage.googleapis.com/v1beta"; + var jsonOptions = new JsonSerializerOptions { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }; + + // Build single inline request + var batchBody = new + { + batch = new + { + display_name = "mindforge-batch", + input_config = new + { + requests = new + { + requests = new[] + { + new + { + request = new + { + system_instruction = string.IsNullOrEmpty(systemPrompt) ? null : new + { + parts = new[] { new { text = systemPrompt } } + }, + contents = new[] + { + new + { + role = "user", + parts = new[] { new { text = userPrompt } } + } + } + }, + metadata = new { key = "request-1" } + } + } + } + } + } + }; + + // Submit batch job + var createUrl = $"{apiBase}/models/{model}:batchGenerateContent?key={apiKey}"; + using var createReq = new HttpRequestMessage(HttpMethod.Post, createUrl); + createReq.Content = new StringContent(JsonSerializer.Serialize(batchBody, jsonOptions), Encoding.UTF8, "application/json"); + + var createResp = await _httpClient.SendAsync(createReq); + var createBody = await createResp.Content.ReadAsStringAsync(); + if (!createResp.IsSuccessStatusCode) + throw new Exception($"Gemini Batch API error creating job {(int)createResp.StatusCode}: {createBody}"); + + _logger.LogInformation("Gemini Batch API job created"); + + var createResult = JsonSerializer.Deserialize(createBody); + if (!createResult.TryGetProperty("name", out var nameEl)) + throw new Exception("Gemini Batch API did not return a job name."); + + var batchName = nameEl.GetString()!; + var pollUrl = $"{apiBase}/{batchName}?key={apiKey}"; + + _logger.LogInformation("Gemini Batch API job name: {BatchName}", batchName); + + // Poll until terminal state + while (true) + { + await Task.Delay(TimeSpan.FromSeconds(10)); + + using var pollReq = new HttpRequestMessage(HttpMethod.Get, pollUrl); + var pollResp = await _httpClient.SendAsync(pollReq); + var pollBody = await pollResp.Content.ReadAsStringAsync(); + + if (!pollResp.IsSuccessStatusCode) + throw new Exception($"Gemini Batch API error polling status {(int)pollResp.StatusCode}: {pollBody}"); + + var pollResult = JsonSerializer.Deserialize(pollBody); + var metadata = pollResult.GetProperty("metadata"); + var state = metadata.GetProperty("state"); + + _logger.LogInformation("Gemini Batch API job state: {State}", state.GetString()); + + switch (state.GetString()) + { + case "BATCH_STATE_SUCCEEDED": + if (pollResult.TryGetProperty("response", out var batchResponse) && + batchResponse.TryGetProperty("inlinedResponses", out var inlinedResponses) && + inlinedResponses.TryGetProperty("inlinedResponses", out var inlinedResponsesInternal) && + inlinedResponsesInternal.GetArrayLength() > 0) + { + _logger.LogInformation("Gemini Batch API job succeeded"); + + var first = inlinedResponsesInternal[0]; + if (first.TryGetProperty("error", out var reqError)) + throw new Exception($"Gemini Batch request error: {reqError}"); + + if (first.TryGetProperty("response", out var innerResponse) && + innerResponse.TryGetProperty("candidates", out var candidates) && + candidates.GetArrayLength() > 0) + { + var content = candidates[0].GetProperty("content"); + if (content.TryGetProperty("parts", out var parts) && parts.GetArrayLength() > 0) + return parts[0].GetProperty("text").GetString() ?? string.Empty; + } + } + throw new Exception("Gemini Batch job succeeded but returned no content."); + + case "BATCH_STATE_FAILED": + throw new Exception($"Gemini Batch job failed: {pollBody}"); + + case "BATCH_STATE_CANCELLED": + throw new Exception("Gemini Batch job was cancelled."); + + case "BATCH_STATE_EXPIRED": + throw new Exception("Gemini Batch job expired before completing."); + + // BATCH_STATE_PENDING / BATCH_STATE_RUNNING — keep polling + } + } + } + } +} diff --git a/Mindforge.API/Providers/ILlmApiProvider.cs b/Mindforge.API/Providers/ILlmApiProvider.cs new file mode 100644 index 0000000..2bd756a --- /dev/null +++ b/Mindforge.API/Providers/ILlmApiProvider.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Mindforge.API.Providers +{ + public interface ILlmApiProvider + { + Task SendRequestAsync(string systemPrompt, string userPrompt, string model); + Task SendRequestBatchAsync(string systemPrompt, string userPrompt, string model); + } +} diff --git a/Mindforge.API/Providers/OpenAIApiProvider.cs b/Mindforge.API/Providers/OpenAIApiProvider.cs new file mode 100644 index 0000000..3870430 --- /dev/null +++ b/Mindforge.API/Providers/OpenAIApiProvider.cs @@ -0,0 +1,110 @@ +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + +namespace Mindforge.API.Providers +{ + public class OpenAIApiProvider : ILlmApiProvider + { + private readonly HttpClient _httpClient; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public OpenAIApiProvider(HttpClient httpClient, IConfiguration configuration, ILogger logger) + { + _httpClient = httpClient; + _httpClient.Timeout = TimeSpan.FromMinutes(5); + _configuration = configuration; + _logger = logger; + } + + public async Task SendRequestAsync(string systemPrompt, string userPrompt, string model) + { + var apiKey = _configuration["OPENAI_API_KEY"]; + if (string.IsNullOrEmpty(apiKey)) + { + throw new Exception("OPENAI_API_KEY not found in configuration."); + } + + var apiBase = "https://api.openai.com/v1"; + var url = $"{apiBase.TrimEnd('/')}/responses"; + + var reqBody = new + { + model = model, + input = new[] + { + new { role = "developer", content = systemPrompt }, + new { role = "user", content = userPrompt } + }, + reasoning = new + { + effort = "low" + } + }; + + var jsonBody = JsonSerializer.Serialize(reqBody); + + Exception? lastErr = null; + + for (int i = 0; i < 5; i++) + { + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); + request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + try + { + var response = await _httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + lastErr = new Exception($"OpenAI API error status {(int)response.StatusCode}: {responseBody}"); + await Task.Delay(TimeSpan.FromSeconds(1 << i)); + continue; + } + + var result = JsonSerializer.Deserialize(responseBody); + if (result.TryGetProperty("output", out var outputArray)) + { + foreach (var outputItem in outputArray.EnumerateArray()) + { + if (outputItem.TryGetProperty("content", out var contentArray)) + { + foreach (var contentItem in contentArray.EnumerateArray()) + { + if (contentItem.TryGetProperty("text", out var textContent)) + { + return textContent.GetString() ?? string.Empty; + } + } + } + } + } + + _logger.LogWarning("OpenAI API raw response: {responseBody}", responseBody); + + throw new Exception("empty response from OpenAI API"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in OpenAI API request"); + lastErr = ex; + await Task.Delay(TimeSpan.FromSeconds(1 << i)); + } + } + + throw new Exception($"failed to get OpenAI response after 5 attempts. Last error: {lastErr?.Message}", lastErr); + } + + public async Task SendRequestBatchAsync(string systemPrompt, string userPrompt, string model) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mindforge.API/Services/AgentService.cs b/Mindforge.API/Services/AgentService.cs new file mode 100644 index 0000000..ec0ab52 --- /dev/null +++ b/Mindforge.API/Services/AgentService.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Mindforge.API.Models.Enums; +using Mindforge.API.Providers; +using Mindforge.API.Services.Interfaces; + +namespace Mindforge.API.Services +{ + public class AgentService : IAgentService + { + private readonly IEnumerable _providers; + + public AgentService(IEnumerable providers) + { + _providers = providers; + } + + public Task ProcessRequestAsync(LlmProvider providerEnum, string systemPrompt, string userPrompt, string model) + { + ILlmApiProvider provider = providerEnum switch + { + LlmProvider.OpenAI => _providers.OfType().FirstOrDefault() + ?? throw new Exception("OpenAI provider not registered"), + LlmProvider.Gemini => _providers.OfType().FirstOrDefault() + ?? throw new Exception("Gemini provider not registered"), + _ => throw new Exception("Unknown provider") + }; + + return provider.SendRequestAsync(systemPrompt, userPrompt, model); + } + + public Task ProcessRequestBatchAsync(LlmProvider providerEnum, string systemPrompt, string userPrompt, string model) + { + ILlmApiProvider provider = providerEnum switch + { + LlmProvider.OpenAI => _providers.OfType().FirstOrDefault() + ?? throw new Exception("OpenAI provider not registered"), + LlmProvider.Gemini => _providers.OfType().FirstOrDefault() + ?? throw new Exception("Gemini provider not registered"), + _ => throw new Exception("Unknown provider") + }; + + return provider.SendRequestBatchAsync(systemPrompt, userPrompt, model); + } + } +} diff --git a/Mindforge.API/Services/FileService.cs b/Mindforge.API/Services/FileService.cs new file mode 100644 index 0000000..226e6dd --- /dev/null +++ b/Mindforge.API/Services/FileService.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using Mindforge.API.Models.Enums; +using Mindforge.API.Models.Requests; +using Mindforge.API.Services.Interfaces; +using Mindforge.API.Exceptions; + +namespace Mindforge.API.Services +{ + public class FileService : IFileService + { + private readonly IAgentService _agentService; + + private const LlmProvider DefaultProvider = LlmProvider.OpenAI; + private const string DefaultModel = "gpt-5-mini"; + + public FileService(IAgentService agentService) + { + _agentService = agentService; + } + + public async Task CheckFileAsync(FileCheckRequest request) + { + string systemPrompt; + + if (request.CheckType.ToLower() == "language") + { + systemPrompt = $@"Você é um revisor de textos sênior e especialista em língua portuguesa do Brasil. +Sua tarefa é analisar o texto fornecido e corrigir rigorosamente todos os erros gramaticais, de concordância, pontuação, garantindo clareza, coesão e fluidez. +Por favor, siga esta estrutura: +1. Retorne o texto completamente revisado e polido. +2. Não adicione nenhum texto adicional, apenas o texto revisado. +3. Não altere o sentido do texto, mantenha a mesma ideia. Apenas corrija os erros gramaticais, de concordância e pontuação. +Responda única e exclusivamente em português do Brasil. +Foque em textos de concursos públicos, principalmente para a banca Cebraspe."; + } + else if (request.CheckType.ToLower() == "content") + { + systemPrompt = $@"Você é um analista de conteúdo experiente, especializado em revisão crítica e verificação de fatos. +Sua tarefa é realizar uma análise rigorosa do texto fornecido, identificando erros lógicos, imprecisões de conteúdo, contradições internas e inconsistências argumentativas. +Por favor, siga esta estrutura: +1. Destaque cada problema encontrado no texto original. +2. Explique detalhadamente por que é um erro ou inconsistência. +3. Apresente sugestões claras e embasadas para reescrever ou corrigir os trechos problemáticos, melhorando a coerência e a lógica e a clareza do conteúdo. +Responda única e exclusivamente em português do Brasil e mantenha um tom analítico e construtivo. + +Responda em tópicos para ser apresentados ao usuário, sendo sucinto e não extremamente detalhado. +Os resumos serão utilizados para concursos públicos, principalmente para a banca Cebraspe. +"; + } + else + { + throw new UserException("Tipo de verificação inválido. Use 'language' ou 'content'."); + } + + string userPrompt = $"Arquivo: {request.FileName}\nConteúdo:\n{request.FileContent}"; + + return await _agentService.ProcessRequestAsync(DefaultProvider, systemPrompt, userPrompt, DefaultModel); + } + } +} diff --git a/Mindforge.API/Services/FlashcardService.cs b/Mindforge.API/Services/FlashcardService.cs new file mode 100644 index 0000000..4c5172b --- /dev/null +++ b/Mindforge.API/Services/FlashcardService.cs @@ -0,0 +1,83 @@ +using System.Threading.Tasks; +using Mindforge.API.Models.Enums; +using Mindforge.API.Models.Requests; +using Mindforge.API.Services.Interfaces; + +namespace Mindforge.API.Services +{ + public class FlashcardService : IFlashcardService + { + private readonly IAgentService _agentService; + private readonly ILogger _logger; + + private const LlmProvider DefaultProvider = LlmProvider.Gemini; + private string DefaultModel = "gemini-3.1-flash-image-preview"; + + public FlashcardService(IAgentService agentService, ILogger logger) + { + _agentService = agentService; + _logger = logger; + } + + public async Task GenerateFlashcardsAsync(FlashcardGenerateRequest request) + { + var extraPrompt = ""; + + switch (request.Mode) + { + case FlashcardMode.Basic: + DefaultModel = "gemini-3.1-flash-lite-preview"; + break; + case FlashcardMode.Simple: + DefaultModel = "gemini-3.1-flash-image-preview"; + break; + case FlashcardMode.Detailed: + DefaultModel = "gemini-3.1-flash-image-preview"; + extraPrompt = "Crie flashcards mais detalhados."; + break; + case FlashcardMode.Hyper: + DefaultModel = "gemini-3.1-pro-preview"; + extraPrompt = "Adicione também pequenas questões para fixação, para que o usuário possa testar seus conhecimentos. As questões devem ser curtas e objetivas, como se fosse cobradas em prova mesmo."; + break; + } + + string systemPrompt = $@"Você é um assistente educacional especializado em criar flashcards para o Anki. +Baseado no texto fornecido, crie exatamente {request.Amount} flashcards que focam nos conceitos mais importantes e difíceis. +A resposta FINAL deve ser APENAS no formato CSV, pronto para importação no Anki, sem nenhum texto adicional antes ou depois. +O formato CSV deve ter duas colunas: a frente da carta (pergunta/conceito) e o verso (resposta/explicação). Use ponto e vírgula (;) como separador. Não adicione o cabeçalho. +As perguntas e respostas devem estar estritamente em Português do Brasil. + +Exemplo de saída: +""Qual é a capital do Brasil?"";""Brasília"" +""Qual é a maior cidade do Brasil?"";""São Paulo"" + +Com base no arquivo fornecido, crie exatamente {request.Amount} flashcards que focam nos conceitos mais importantes e difíceis. +{extraPrompt} +"; + + string userPrompt = $"Arquivo: {request.FileName}\nConteúdo:\n{request.FileContent}"; + + //var result = await _agentService.ProcessRequestAsync(DefaultProvider, systemPrompt, userPrompt, DefaultModel); + var result = await _agentService.ProcessRequestBatchAsync(DefaultProvider, systemPrompt, userPrompt, DefaultModel); + + var lines = result.Split('\n'); + + if (lines.Length == 0) + { + throw new Exception("Nenhum flashcard gerado."); + } + + if (lines.Length > request.Amount) + { + _logger.LogWarning("Quantidade de flashcards excede o limite."); + } + + if (lines.Length < request.Amount) + { + _logger.LogWarning("Quantidade de flashcards abaixo do limite."); + } + + return result; + } + } +} diff --git a/Mindforge.API/Services/Interfaces/IAgentService.cs b/Mindforge.API/Services/Interfaces/IAgentService.cs new file mode 100644 index 0000000..55293cc --- /dev/null +++ b/Mindforge.API/Services/Interfaces/IAgentService.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Mindforge.API.Models.Enums; + +namespace Mindforge.API.Services.Interfaces +{ + public interface IAgentService + { + Task ProcessRequestAsync(LlmProvider provider, string systemPrompt, string userPrompt, string model); + Task ProcessRequestBatchAsync(LlmProvider provider, string systemPrompt, string userPrompt, string model); + } +} diff --git a/Mindforge.API/Services/Interfaces/IFileService.cs b/Mindforge.API/Services/Interfaces/IFileService.cs new file mode 100644 index 0000000..84b4b89 --- /dev/null +++ b/Mindforge.API/Services/Interfaces/IFileService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Mindforge.API.Models.Requests; + +namespace Mindforge.API.Services.Interfaces +{ + public interface IFileService + { + Task CheckFileAsync(FileCheckRequest request); + } +} diff --git a/Mindforge.API/Services/Interfaces/IFlashcardService.cs b/Mindforge.API/Services/Interfaces/IFlashcardService.cs new file mode 100644 index 0000000..ec8da2d --- /dev/null +++ b/Mindforge.API/Services/Interfaces/IFlashcardService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Mindforge.API.Models.Requests; + +namespace Mindforge.API.Services.Interfaces +{ + public interface IFlashcardService + { + Task GenerateFlashcardsAsync(FlashcardGenerateRequest request); + } +} diff --git a/Mindforge.API/appsettings.json b/Mindforge.API/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Mindforge.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Mindforge.API/deploy/mindforge-api.yaml b/Mindforge.API/deploy/mindforge-api.yaml index 08bdbf6..34facfc 100644 --- a/Mindforge.API/deploy/mindforge-api.yaml +++ b/Mindforge.API/deploy/mindforge-api.yaml @@ -49,3 +49,24 @@ spec: ports: - port: 80 targetPort: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mindforge-api + namespace: mindforge + labels: + app.kubernetes.io/name: mindforge-api +spec: + ingressClassName: nginx + rules: + - host: "api.mindforge.haven" + http: + paths: + - path: "/" + pathType: Prefix + backend: + service: + name: mindforge-api + port: + number: 80 diff --git a/Mindforge.Web/.env b/Mindforge.Web/.env new file mode 100644 index 0000000..b54eccf --- /dev/null +++ b/Mindforge.Web/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:5123 diff --git a/Mindforge.Web/.env.dev b/Mindforge.Web/.env.dev new file mode 100644 index 0000000..b54eccf --- /dev/null +++ b/Mindforge.Web/.env.dev @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://localhost:5123 diff --git a/Mindforge.Web/.gitignore b/Mindforge.Web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/Mindforge.Web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Mindforge.Web/deploy/mindforge-web.yaml b/Mindforge.Web/deploy/mindforge-web.yaml index 19816c6..4b0e167 100644 --- a/Mindforge.Web/deploy/mindforge-web.yaml +++ b/Mindforge.Web/deploy/mindforge-web.yaml @@ -38,3 +38,24 @@ spec: ports: - port: 80 targetPort: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mindforge-web + namespace: mindforge + labels: + app.kubernetes.io/name: mindforge-web +spec: + ingressClassName: nginx + rules: + - host: "mindforge.haven" + http: + paths: + - path: "/" + pathType: Prefix + backend: + service: + name: mindforge-web + port: + number: 80 diff --git a/Mindforge.Web/index.html b/Mindforge.Web/index.html new file mode 100644 index 0000000..d0f0e28 --- /dev/null +++ b/Mindforge.Web/index.html @@ -0,0 +1,16 @@ + + + + + + + + Mindforge + + + +
+ + + + \ No newline at end of file diff --git a/Mindforge.Web/package-lock.json b/Mindforge.Web/package-lock.json new file mode 100644 index 0000000..685766c --- /dev/null +++ b/Mindforge.Web/package-lock.json @@ -0,0 +1,1897 @@ +{ + "name": "mindforge-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mindforge-web", + "version": "0.0.0", + "dependencies": { + "@types/marked": "^5.0.2", + "diff": "^8.0.3", + "marked": "^17.0.4", + "preact": "^10.29.0" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.4", + "@types/diff": "^7.0.2", + "@types/node": "^24.12.0", + "typescript": "~5.9.3", + "vite": "^8.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", + "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@preact/preset-vite": { + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.4.tgz", + "integrity": "sha512-L7RQRs2GiG0lLUz7JSg07vU6lhlzdIthH0eqYZmRR70tTB9ikKCq2LHr+PZzhzIOco3Dioi6P6e/fjAmDUMJbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@prefresh/vite": "^2.4.11", + "@rollup/pluginutils": "^5.0.0", + "babel-plugin-transform-hook-names": "^1.0.2", + "debug": "^4.4.3", + "magic-string": "^0.30.21", + "picocolors": "^1.1.1", + "vite-prerender-plugin": "^0.5.8", + "zimmerframe": "^1.1.4" + }, + "peerDependencies": { + "@babel/core": "7.x", + "vite": "2.x || 3.x || 4.x || 5.x || 6.x || 7.x || 8.x" + } + }, + "node_modules/@prefresh/babel-plugin": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@prefresh/babel-plugin/-/babel-plugin-0.5.3.tgz", + "integrity": "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@prefresh/core": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/@prefresh/core/-/core-1.5.9.tgz", + "integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "preact": "^10.0.0 || ^11.0.0-0" + } + }, + "node_modules/@prefresh/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@prefresh/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@prefresh/vite": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@prefresh/vite/-/vite-2.4.12.tgz", + "integrity": "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.1", + "@prefresh/babel-plugin": "^0.5.2", + "@prefresh/core": "^1.5.0", + "@prefresh/utils": "^1.2.0", + "@rollup/pluginutils": "^4.2.1" + }, + "peerDependencies": { + "preact": "^10.4.0 || ^11.0.0-0", + "vite": ">=2.0.0" + } + }, + "node_modules/@prefresh/vite/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@prefresh/vite/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", + "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", + "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", + "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", + "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", + "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", + "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", + "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", + "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/diff": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.2.tgz", + "integrity": "sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/babel-plugin-transform-hook-names": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz", + "integrity": "sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.12.10" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz", + "integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-html-parser": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz", + "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", + "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.120.0", + "@rolldown/pluginutils": "1.0.0-rc.10" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", + "@rolldown/binding-darwin-x64": "1.0.0-rc.10", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/simple-code-frame": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz", + "integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "kolorist": "^1.6.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "1.0.0-pre2", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", + "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", + "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.10", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-prerender-plugin": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.13.tgz", + "integrity": "sha512-IKSpYkzDBsKAxa05naRbj7GvNVMSdww/Z/E89oO3xndz+gWnOBOKOAbEXv7qDhktY/j3vHgJmoV1pPzqU2tx9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "kolorist": "^1.8.0", + "magic-string": "0.x >= 0.26.0", + "node-html-parser": "^6.1.12", + "simple-code-frame": "^1.3.0", + "source-map": "^0.7.4", + "stack-trace": "^1.0.0-pre2" + }, + "peerDependencies": { + "vite": "5.x || 6.x || 7.x || 8.x" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/Mindforge.Web/package.json b/Mindforge.Web/package.json new file mode 100644 index 0000000..149523e --- /dev/null +++ b/Mindforge.Web/package.json @@ -0,0 +1,24 @@ +{ + "name": "mindforge-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@types/marked": "^5.0.2", + "diff": "^8.0.3", + "marked": "^17.0.4", + "preact": "^10.29.0" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.4", + "@types/diff": "^7.0.2", + "@types/node": "^24.12.0", + "typescript": "~5.9.3", + "vite": "^8.0.1" + } +} diff --git a/Mindforge.Web/public/assets/mindforge-banner.png b/Mindforge.Web/public/assets/mindforge-banner.png new file mode 100644 index 0000000..4ade2f7 Binary files /dev/null and b/Mindforge.Web/public/assets/mindforge-banner.png differ diff --git a/Mindforge.Web/public/assets/mindforge.png b/Mindforge.Web/public/assets/mindforge.png new file mode 100644 index 0000000..58a7012 Binary files /dev/null and b/Mindforge.Web/public/assets/mindforge.png differ diff --git a/Mindforge.Web/public/favicon.ico b/Mindforge.Web/public/favicon.ico new file mode 100644 index 0000000..bca141f Binary files /dev/null and b/Mindforge.Web/public/favicon.ico differ diff --git a/Mindforge.Web/src/app.css b/Mindforge.Web/src/app.css new file mode 100644 index 0000000..186e0ee --- /dev/null +++ b/Mindforge.Web/src/app.css @@ -0,0 +1,62 @@ +.home-hero { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + animation: fadeIn 0.8s ease-out; +} + +.hero-title { + font-size: 4rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 1rem; + text-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + background: linear-gradient(90deg, #f4f5f5, #00b4d8); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.hero-subtitle { + font-size: 1.5rem; + color: rgba(244, 245, 245, 0.8); + font-weight: 300; + letter-spacing: 1px; +} + +.module-content { + width: 100%; + max-width: 800px; + margin: 0 auto; + animation: slideUp 0.5s ease-out; +} + +.subtitle { + font-size: 1.2rem; + color: rgba(244, 245, 245, 0.8); + margin-bottom: 2rem; +} + +.placeholder-box { + background: rgba(255, 255, 255, 0.05); + border: 1px dashed rgba(255, 255, 255, 0.2); + border-radius: 12px; + padding: 4rem 2rem; + font-size: 1.5rem; + color: rgba(244, 245, 245, 0.5); + text-align: center; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} diff --git a/Mindforge.Web/src/app.tsx b/Mindforge.Web/src/app.tsx new file mode 100644 index 0000000..3694a16 --- /dev/null +++ b/Mindforge.Web/src/app.tsx @@ -0,0 +1,34 @@ +import { useState } from 'preact/hooks'; +import './app.css'; +import { Header } from './components/Header'; +import { Sidebar } from './components/Sidebar'; +import { VerificadorComponent } from './components/VerificadorComponent'; +import { FlashcardComponent } from './components/FlashcardComponent'; + +export function App() { + const [activeModule, setActiveModule] = useState<'home' | 'verificador' | 'flashcards'>('home'); + + return ( + <> +
setActiveModule('home')} /> +
+ +
+
+
+ Mindforge Banner +

Mindforge! - STAY HARD!

+

Sua ferramenta de forja mental e estudos.

+
+
+
+ +
+
+ +
+
+
+ + ); +} diff --git a/Mindforge.Web/src/components/Button.css b/Mindforge.Web/src/components/Button.css new file mode 100644 index 0000000..5e93a4d --- /dev/null +++ b/Mindforge.Web/src/components/Button.css @@ -0,0 +1,41 @@ +.btn { + font-family: var(--font-main); + font-weight: 700; + font-size: 1rem; + padding: 0.8rem 1.5rem; + border-radius: 8px; + border: none; + cursor: pointer; + outline: none; + transition: all 0.3s ease; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + letter-spacing: 0.5px; +} + +.btn-primary { + background: rgba(255, 255, 255, 0.1); + color: var(--color-text-creamy); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-primary:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.btn-primary:active { + transform: translateY(0); +} + +.btn-secondary { + background: transparent; + color: var(--color-text-creamy); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.05); +} diff --git a/Mindforge.Web/src/components/Button.tsx b/Mindforge.Web/src/components/Button.tsx new file mode 100644 index 0000000..f9095b3 --- /dev/null +++ b/Mindforge.Web/src/components/Button.tsx @@ -0,0 +1,22 @@ +import type { ComponentChildren } from 'preact'; +import './Button.css'; + +interface ButtonProps extends preact.JSX.HTMLAttributes { + children: ComponentChildren; + variant?: 'primary' | 'secondary'; + className?: string; + onClick?: (e?: any) => any; + disabled?: boolean; + style?: any; +} + +export function Button({ children, variant = 'primary', className = '', ...props }: ButtonProps) { + return ( + + ); +} diff --git a/Mindforge.Web/src/components/FlashcardComponent.css b/Mindforge.Web/src/components/FlashcardComponent.css new file mode 100644 index 0000000..38fee80 --- /dev/null +++ b/Mindforge.Web/src/components/FlashcardComponent.css @@ -0,0 +1,207 @@ +.flashcard-container { + width: 100%; + max-width: 800px; + margin: 0 auto; + animation: slideUp 0.5s ease-out; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.flashcard-form { + display: flex; + flex-direction: column; + gap: 1.2rem; + background: rgba(255, 255, 255, 0.05); + padding: 2rem; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.input-group { + display: flex; + flex-direction: column; + gap: 0.8rem; + text-align: left; +} + +.input-group label { + font-weight: 700; + color: var(--color-text-creamy); +} + +.text-area { + width: 100%; + min-height: 200px; + background: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + padding: 1rem; + color: var(--color-text-creamy); + font-family: inherit; + font-size: 1rem; + resize: vertical; +} + +.text-area:focus { + outline: none; + border-color: var(--color-accent); +} + +.file-input-wrapper { + display: flex; + align-items: center; + gap: 1rem; +} + +.file-input { + display: none; +} + +.file-input-label { + background: var(--color-sidebar); + color: var(--color-text-creamy); + padding: 0.6rem 1.2rem; + border-radius: 6px; + cursor: pointer; + border: 1px solid rgba(255, 255, 255, 0.2); + transition: all 0.2s ease; +} + +.file-input-label:hover { + background: rgba(255, 255, 255, 0.1); + border-color: var(--color-accent); +} + +.slider-wrapper { + display: flex; + align-items: center; + gap: 1rem; +} + +.slider-input { + flex: 1; + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 8px; + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + outline: none; +} + +.slider-input::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--color-accent); + cursor: pointer; + transition: transform 0.1s; +} + +.slider-input::-webkit-slider-thumb:hover { + transform: scale(1.2); +} + +.amount-display { + font-weight: 700; + color: var(--color-accent); + min-width: 40px; + text-align: right; + font-size: 1.1rem; +} + +/* Spinner */ +.spinner-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem 0; + gap: 1rem; +} + +.spinner { + width: 50px; + height: 50px; + border: 4px solid rgba(255, 255, 255, 0.1); + border-left-color: var(--color-accent); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.success-message { + color: #7ee787; + text-align: center; + font-weight: 700; + margin-top: 1rem; + animation: fadeIn 0.5s ease-out; +} + +.radio-group { + display: flex; + flex-wrap: wrap; + background: rgba(0, 0, 0, 0.3); + padding: 4px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); + width: fit-content; + overflow: hidden; + gap: 4px; +} + +.radio-item { + position: relative; + flex: 1; + min-width: 100px; +} + +.radio-item input[type="radio"] { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.radio-label { + display: flex; + align-items: center; + justify-content: center; + padding: 0.8rem 1.2rem; + cursor: pointer; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.6); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + white-space: nowrap; +} + +.radio-item input[type="radio"]:checked + .radio-label { + background: var(--color-accent); + color: #000; + box-shadow: 0 4px 12px rgba(var(--color-accent-rgb), 0.3); +} + +.radio-item:hover .radio-label:not(.radio-item input[type="radio"]:checked + .radio-label) { + background: rgba(255, 255, 255, 0.05); + color: #fff; +} + +/* Animations for selection */ +.radio-item input[type="radio"]:checked + .radio-label { + animation: selectBounce 0.3s ease-out; +} + +@keyframes selectBounce { + 0% { transform: scale(0.95); } + 50% { transform: scale(1.02); } + 100% { transform: scale(1); } +} diff --git a/Mindforge.Web/src/components/FlashcardComponent.tsx b/Mindforge.Web/src/components/FlashcardComponent.tsx new file mode 100644 index 0000000..accda7e --- /dev/null +++ b/Mindforge.Web/src/components/FlashcardComponent.tsx @@ -0,0 +1,209 @@ +import { useState, useRef } from 'preact/hooks'; +import { MindforgeApiService, type FlashcardMode } from '../services/MindforgeApiService'; + +// Mapping of flashcard mode to its maximum allowed amount +const modeMax: Record = { + Basic: 25, + Simple: 30, + Detailed: 70, + Hyper: 130, +}; + +import { Button } from './Button'; +import './FlashcardComponent.css'; + +function utf8ToBase64(str: string): string { + const bytes = new TextEncoder().encode(str); + const binary = Array.from(bytes, byte => String.fromCharCode(byte)).join(''); + return window.btoa(binary); +} + +export function FlashcardComponent() { + const [text, setText] = useState(''); + const [fileName, setFileName] = useState('manual_input.md'); + const [amount, setAmount] = useState(20); + const [mode, setMode] = useState('Simple'); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const handleModeChange = (newMode: FlashcardMode) => { + setMode(newMode); // set the mode + setAmount(20); // set the default amount + }; + + const fileInputRef = useRef(null); + + const handleFileUpload = (e: Event) => { + const target = e.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + const file = target.files[0]; + setFileName(file.name); + const reader = new FileReader(); + reader.onload = (event) => { + if (event.target?.result) { + setText(event.target.result as string); + } + }; + reader.readAsText(file); + } + }; + + const handleGenerate = async () => { + if (!text.trim()) { + setError('Por favor, insira algum texto ou faça upload de um arquivo para gerar os flashcards.'); + return; + } + + setLoading(true); + setError(null); + setSuccess(false); + + try { + const base64Content = utf8ToBase64(text); + const res = await MindforgeApiService.generateFlashcards({ + fileContent: base64Content, + fileName, + amount, + mode + }); + + const csvContent = res.result; + downloadCSV(csvContent); + setSuccess(true); + } catch (err: any) { + setError(err.message || 'Ocorreu um erro ao gerar os flashcards.'); + } finally { + setLoading(false); + } + }; + + const downloadCSV = (content: string) => { + // Adicionar BOM do UTF-8 para o Excel reconhecer os caracteres corretamente + const bom = new Uint8Array([0xEF, 0xBB, 0xBF]); + const blob = new Blob([bom, content], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', `flashcards_${Date.now()}.csv`); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }; + + return ( +
+

Gerador de Flashcards

+

Crie flashcards baseados nos seus materiais de estudo rapidamente.

+ +
+
+ +