- introduce `LyricFlow.Core.Backend` with shared DTOs, rhyme/spellcheck engines, and REST endpoints - wire Python GUI/core to run and call the backend via new bridge/client modules - add backend parity/integration tests and update packaging/ignore settings
295 lines
9.6 KiB
C#
295 lines
9.6 KiB
C#
using System.Text.RegularExpressions;
|
|
using LyricFlow.Backend.Api;
|
|
using LyricFlow.Core.Dtos;
|
|
using LyricFlow.Core.Engine;
|
|
using LyricFlow.Core.Services;
|
|
using LyricFlow.Core.Storage;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Http.Json;
|
|
|
|
// MARK: - Application Bootstrap
|
|
#region Application Bootstrap
|
|
|
|
var builder = WebApplication.CreateSlimBuilder(args);
|
|
builder.WebHost.UseUrls(builder.Configuration["Backend:Urls"] ?? "http://127.0.0.1:5000");
|
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
|
{
|
|
options.SerializerOptions.TypeInfoResolverChain.Insert(0, ApiJsonSerializerContext.Default);
|
|
});
|
|
|
|
var dbPath = builder.Configuration["Database:Path"] ?? "data/lyricflow.db";
|
|
builder.Services.AddSingleton(new StorageService(dbPath));
|
|
|
|
var cmuDictPath = NltkPathResolver.ResolveCmudictPath(builder.Configuration["Nltk:CmudictPath"]);
|
|
var wordNetPath = NltkPathResolver.ResolveWordNetPath(builder.Configuration["Nltk:WordNetPath"]);
|
|
var processor = new PhoneticProcessor(cmuDictPath);
|
|
builder.Services.AddSingleton(processor);
|
|
builder.Services.AddSingleton(new WordNetLexicon(wordNetPath));
|
|
builder.Services.AddSingleton(sp => new RhymeEngine(processor, sp.GetRequiredService<WordNetLexicon>()));
|
|
builder.Services.AddSingleton(sp => new SpellcheckEngine(processor, sp.GetRequiredService<WordNetLexicon>()));
|
|
builder.Services.AddSingleton(new ProjectStateService());
|
|
builder.Services.AddSingleton(new SessionStoreService(builder.Configuration["State:SessionPath"]));
|
|
builder.Services.AddSingleton(new CorePreferencesStore(builder.Configuration["State:CorePreferencesPath"]));
|
|
builder.Services.AddSingleton(new FileService());
|
|
|
|
var app = builder.Build();
|
|
|
|
#endregion
|
|
|
|
// MARK: - Health And Analysis Routes
|
|
#region Health And Analysis Routes
|
|
|
|
app.MapGet("/api/health", (PhoneticProcessor phonetics, WordNetLexicon wordNet) =>
|
|
{
|
|
var capabilities = new CapabilitiesDto(
|
|
Analysis: true,
|
|
Phonetics: true,
|
|
Spellcheck: true,
|
|
Synonyms: wordNet.IsAvailable,
|
|
Autocorrect: true,
|
|
Projects: true,
|
|
Sessions: true,
|
|
Settings: true,
|
|
Files: true,
|
|
History: true,
|
|
Scratchpad: true,
|
|
HasCmudict: phonetics.Dictionary.Count > 0,
|
|
HasWordNet: wordNet.IsAvailable
|
|
);
|
|
|
|
return Results.Ok(new HealthDto(
|
|
Status: "healthy",
|
|
Version: "2.0.0",
|
|
Ready: phonetics.Dictionary.Count > 0,
|
|
DictionaryCount: phonetics.Dictionary.Count,
|
|
Capabilities: capabilities
|
|
));
|
|
});
|
|
|
|
app.MapGet("/api/analysis/phonemes", (PhoneticProcessor phonetics, [FromQuery] string word) =>
|
|
{
|
|
return Results.Ok(phonetics.GetPhonemes(word));
|
|
});
|
|
|
|
app.MapGet("/api/analysis/syllables", (RhymeEngine engine, [FromQuery] string word) =>
|
|
{
|
|
return Results.Ok(new CountDto(engine.CountSyllables(word)));
|
|
});
|
|
|
|
app.MapGet("/api/analysis/suggestions", (RhymeEngine engine, [FromQuery] string word, [FromQuery] int? limit) =>
|
|
{
|
|
return Results.Ok(engine.FindSuggestions(word, limit ?? 20));
|
|
});
|
|
|
|
app.MapGet("/api/analysis/synonyms", (RhymeEngine engine, [FromQuery] string word, [FromQuery] int? limit) =>
|
|
{
|
|
return Results.Ok(engine.FindSynonyms(word, limit ?? 15));
|
|
});
|
|
|
|
app.MapPost("/api/analysis/rhyme-groups", (RhymeEngine engine, [FromBody] AnalyzeRequest request) =>
|
|
{
|
|
return Results.Ok(engine.GetRhymeGroups(request.Text));
|
|
});
|
|
|
|
app.MapPost("/api/analysis/line-densities", (RhymeEngine engine, [FromBody] AnalyzeRequest request) =>
|
|
{
|
|
return Results.Ok(CalculateLineDensities(engine, request.Text));
|
|
});
|
|
|
|
app.MapGet("/api/engine/rhymes", (RhymeEngine engine, [FromQuery] string word, [FromQuery] int? limit) =>
|
|
{
|
|
return Results.Ok(engine.FindSuggestions(word, limit ?? 20));
|
|
});
|
|
|
|
app.MapGet("/api/engine/syllables", (RhymeEngine engine, [FromQuery] string word) =>
|
|
{
|
|
return Results.Ok(new CountDto(engine.CountSyllables(word)));
|
|
});
|
|
|
|
app.MapPost("/api/engine/analyze", (RhymeEngine engine, [FromBody] AnalyzeRequest request) =>
|
|
{
|
|
return Results.Ok(engine.GetRhymeGroups(request.Text));
|
|
});
|
|
|
|
app.MapPost("/api/engine/densities", (RhymeEngine engine, [FromBody] AnalyzeRequest request) =>
|
|
{
|
|
return Results.Ok(CalculateLineDensities(engine, request.Text));
|
|
});
|
|
|
|
#endregion
|
|
|
|
// MARK: - Spellcheck And State Routes
|
|
#region Spellcheck And State Routes
|
|
|
|
app.MapGet("/api/spellcheck/known", (SpellcheckEngine engine, [FromQuery] string word) =>
|
|
{
|
|
return Results.Ok(new KnownWordDto(engine.IsKnownWord(word)));
|
|
});
|
|
|
|
app.MapGet("/api/spellcheck/suggestions", (SpellcheckEngine engine, [FromQuery] string word, [FromQuery] int? limit) =>
|
|
{
|
|
return Results.Ok(engine.GetSpellingSuggestions(word, limit ?? 6));
|
|
});
|
|
|
|
app.MapGet("/api/spellcheck/autocorrect", (SpellcheckEngine engine, [FromQuery] string word) =>
|
|
{
|
|
return Results.Ok(new CandidateDto(engine.GetAutocorrectCandidate(word)));
|
|
});
|
|
|
|
app.MapPost("/api/spellcheck/issues", (SpellcheckEngine engine, [FromBody] AnalyzeRequest request, [FromQuery] int? limit) =>
|
|
{
|
|
return Results.Ok(engine.GetTextSpellingIssues(request.Text, limit ?? 6));
|
|
});
|
|
|
|
app.MapPost("/api/projects/read", (ProjectStateService projects, [FromBody] ProjectReadRequest request) =>
|
|
{
|
|
return Results.Ok(projects.ReadProject(request.ProjectFile));
|
|
});
|
|
|
|
app.MapPost("/api/projects/write", (ProjectStateService projects, [FromBody] ProjectWriteRequest request) =>
|
|
{
|
|
projects.WriteProject(request.ProjectFile, request.State);
|
|
return Results.Ok(new SuccessDto(true));
|
|
});
|
|
|
|
app.MapPost("/api/session/load", (SessionStoreService sessionStore, [FromBody] SessionLoadRequest request) =>
|
|
{
|
|
return Results.Ok(sessionStore.Load(request.WorkspaceRoot));
|
|
});
|
|
|
|
app.MapPost("/api/session/save", (SessionStoreService sessionStore, [FromBody] SessionSaveRequest request) =>
|
|
{
|
|
sessionStore.Save(request.WorkspaceRoot, request.Snapshots);
|
|
return Results.Ok(new SuccessDto(true));
|
|
});
|
|
|
|
app.MapPost("/api/session/clear", (SessionStoreService sessionStore, [FromBody] SessionClearRequest request) =>
|
|
{
|
|
sessionStore.Clear(request.WorkspaceRoot);
|
|
return Results.Ok(new SuccessDto(true));
|
|
});
|
|
|
|
app.MapGet("/api/settings", (CorePreferencesStore settingsStore) =>
|
|
{
|
|
return Results.Ok(settingsStore.Load());
|
|
});
|
|
|
|
app.MapPost("/api/settings", (CorePreferencesStore settingsStore, [FromBody] AppCorePreferencesDto preferences) =>
|
|
{
|
|
settingsStore.Save(preferences);
|
|
return Results.Ok(new SuccessDto(true));
|
|
});
|
|
|
|
app.MapPost("/api/files/read", (FileService files, [FromBody] FileReadRequest request) =>
|
|
{
|
|
return Results.Ok(files.ReadFile(request.Path));
|
|
});
|
|
|
|
app.MapPost("/api/files/write", (FileService files, [FromBody] FileWriteRequest request) =>
|
|
{
|
|
return Results.Ok(files.WriteFile(request.Path, request.Content));
|
|
});
|
|
|
|
app.MapPost("/api/files/create", (FileService files, [FromBody] FileCreateRequest request) =>
|
|
{
|
|
return Results.Ok(request.IsDirectory ? files.CreateDirectory(request.Path) : files.CreateFile(request.Path));
|
|
});
|
|
|
|
app.MapPost("/api/files/rename", (FileService files, [FromBody] FileRenameRequest request) =>
|
|
{
|
|
return Results.Ok(files.Rename(request.OldPath, request.NewPath, request.RootPath));
|
|
});
|
|
|
|
app.MapPost("/api/files/delete", (FileService files, [FromBody] FileDeleteRequest request) =>
|
|
{
|
|
return Results.Ok(files.Delete(request.Path, request.RootPath));
|
|
});
|
|
|
|
#endregion
|
|
|
|
// MARK: - Storage Routes
|
|
#region Storage Routes
|
|
|
|
app.MapPost("/api/history/snapshots", async (StorageService storage, [FromBody] SaveSnapshotRequest request) =>
|
|
{
|
|
await storage.SaveSnapshotAsync(request.FilePath, request.Content);
|
|
return Results.Ok();
|
|
});
|
|
|
|
app.MapGet("/api/history/snapshots", async (StorageService storage, [FromQuery] string filePath) =>
|
|
{
|
|
var snapshots = await storage.GetSnapshotsAsync(filePath);
|
|
return Results.Ok(snapshots.Select(snapshot => snapshot.ToDto()).ToList());
|
|
});
|
|
|
|
app.MapPost("/api/snapshots", async (StorageService storage, [FromBody] SaveSnapshotRequest request) =>
|
|
{
|
|
await storage.SaveSnapshotAsync(request.FilePath, request.Content);
|
|
return Results.Ok();
|
|
});
|
|
|
|
app.MapGet("/api/snapshots", async (StorageService storage, [FromQuery] string filePath) =>
|
|
{
|
|
var snapshots = await storage.GetSnapshotsAsync(filePath);
|
|
return Results.Ok(snapshots.Select(snapshot => snapshot.ToDto()).ToList());
|
|
});
|
|
|
|
app.MapPost("/api/scratchpad", async (StorageService storage, [FromBody] SaveScratchpadRequest request) =>
|
|
{
|
|
await storage.SaveScratchpadAsync(request.ProjectId, request.Content);
|
|
return Results.Ok();
|
|
});
|
|
|
|
app.MapGet("/api/scratchpad/{projectId}", async (StorageService storage, string projectId) =>
|
|
{
|
|
return Results.Ok(new ScratchpadContentDto(await storage.GetScratchpadAsync(projectId)));
|
|
});
|
|
|
|
app.Run();
|
|
|
|
#endregion
|
|
|
|
// MARK: - Route Utilities
|
|
#region Route Utilities
|
|
|
|
static List<double> CalculateLineDensities(RhymeEngine engine, string text)
|
|
{
|
|
var lines = text.Split('\n');
|
|
var groups = engine.GetRhymeGroups(text);
|
|
using var groupIter = groups.GetEnumerator();
|
|
|
|
var densities = new List<double>();
|
|
foreach (var line in lines)
|
|
{
|
|
var stripped = line.Trim();
|
|
if (stripped.StartsWith("#") || stripped.StartsWith("@") || stripped.StartsWith(">"))
|
|
{
|
|
densities.Add(0.0);
|
|
continue;
|
|
}
|
|
|
|
var analysisText = Regex.Replace(line, @"\[.*?\]", "");
|
|
var words = Regex.Matches(analysisText, @"\b\w+\b");
|
|
if (words.Count == 0)
|
|
{
|
|
densities.Add(0.0);
|
|
continue;
|
|
}
|
|
|
|
var rhymeCount = 0;
|
|
for (var index = 0; index < words.Count; index++)
|
|
{
|
|
if (groupIter.MoveNext() && groupIter.Current.Group is not null)
|
|
{
|
|
rhymeCount++;
|
|
}
|
|
}
|
|
|
|
densities.Add((double)rhymeCount / words.Count);
|
|
}
|
|
|
|
return densities;
|
|
}
|
|
|
|
#endregion
|