194 lines
7.6 KiB
C#
194 lines
7.6 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using PlotLine.Data;
|
|
using PlotLine.Models;
|
|
using PlotLine.Services;
|
|
using PlotLine.ViewModels;
|
|
|
|
namespace PlotLine.Controllers;
|
|
|
|
[Authorize(Policy = "AdminOnly")]
|
|
[Route("Development")]
|
|
public sealed class DevelopmentController(
|
|
IWebHostEnvironment environment,
|
|
ICurrentUserService currentUser,
|
|
IIllustrationLibraryService illustrationLibrary,
|
|
IStoryMemoryService storyMemory,
|
|
IStoryMemoryRepository storyMemoryRepository,
|
|
IStoryIntelligenceVisualisationSnapshotService snapshots) : Controller
|
|
{
|
|
[HttpGet("StoryIntelligenceExperience")]
|
|
public async Task<IActionResult> StoryIntelligenceExperience([FromQuery] string? mode, [FromQuery] int? importSessionId, [FromQuery] int? runId)
|
|
{
|
|
if (!environment.IsDevelopment())
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var requestedMode = string.IsNullOrWhiteSpace(mode) ? "real" : mode.Trim();
|
|
var simulationRequested = string.Equals(requestedMode, "simulation", StringComparison.OrdinalIgnoreCase);
|
|
var replayRequested = string.Equals(requestedMode, "replay", StringComparison.OrdinalIgnoreCase);
|
|
|
|
if (!simulationRequested && importSessionId.HasValue && currentUser.UserId is int userId)
|
|
{
|
|
var realModel = replayRequested
|
|
? await snapshots.BuildReplaySnapshotAsync(importSessionId.Value, userId)
|
|
: await snapshots.BuildImportSessionSnapshotAsync(importSessionId.Value, userId);
|
|
if (realModel is null)
|
|
{
|
|
return View(ErrorModel(
|
|
requestedMode,
|
|
importSessionId,
|
|
$"Import Session {importSessionId.Value:N0} could not be found for the current user, or it has no persisted chapter analysis runs yet."));
|
|
}
|
|
|
|
return View(realModel);
|
|
}
|
|
|
|
if (!simulationRequested && runId.HasValue && currentUser.UserId is int legacyUserId)
|
|
{
|
|
var realModel = await snapshots.BuildSnapshotAsync(runId.Value, legacyUserId);
|
|
if (realModel is null)
|
|
{
|
|
return View(ErrorModel(
|
|
requestedMode,
|
|
null,
|
|
$"Run {runId.Value:N0} could not be found for the current user. Live visualisation URLs should use importSessionId for book imports."));
|
|
}
|
|
|
|
return View(realModel);
|
|
}
|
|
|
|
if (simulationRequested)
|
|
{
|
|
var model = StoryIntelligenceExperiencePrototypeData.Build();
|
|
await illustrationLibrary.ApplyApprovedIllustrationsAsync(model);
|
|
return View(model);
|
|
}
|
|
|
|
if (currentUser.UserId is int selectorUserId)
|
|
{
|
|
var sessions = await snapshots.ListImportSessionOptionsAsync(selectorUserId);
|
|
return View(new StoryIntelligenceExperiencePrototypeViewModel
|
|
{
|
|
Mode = "selector",
|
|
AvailableRuns = sessions,
|
|
ProjectName = "Development",
|
|
BookTitle = "Story Intelligence",
|
|
SnapshotMessage = sessions.Count == 0
|
|
? "No persisted Story Intelligence import sessions were found for the current user. Start a real import, then refresh this page."
|
|
: "Choose a Story Intelligence import session, or explicitly open simulation mode.",
|
|
Diagnostics = new StoryIntelligenceExperienceSnapshotDiagnostics
|
|
{
|
|
RequestedMode = requestedMode,
|
|
RequestedImportSessionId = importSessionId,
|
|
SnapshotSource = "Real"
|
|
}
|
|
});
|
|
}
|
|
|
|
return View(ErrorModel(requestedMode, importSessionId, "No development user is signed in."));
|
|
}
|
|
|
|
[HttpPost("StoryIntelligenceExperience/Rebuild")]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> RebuildStoryIntelligenceExperience(
|
|
[FromForm] int importSessionId,
|
|
[FromForm] string? rebuildScope,
|
|
[FromForm] bool dryRun = false,
|
|
[FromForm] bool queueDemand = false,
|
|
[FromForm] int? maxDemandToQueue = null,
|
|
[FromForm] string? appearancePreference = null)
|
|
{
|
|
if (!environment.IsDevelopment())
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (importSessionId <= 0 || currentUser.UserId is not int userId)
|
|
{
|
|
return BadRequest("A valid importSessionId is required.");
|
|
}
|
|
|
|
var rebuild = await storyMemory.RebuildImportSessionAsync(
|
|
importSessionId,
|
|
userId,
|
|
new StoryMemoryRebuildOptions
|
|
{
|
|
DryRun = dryRun,
|
|
RebuildAll = !string.Equals(rebuildScope, "incremental", StringComparison.OrdinalIgnoreCase),
|
|
QueueDemand = queueDemand,
|
|
MaxDemandToQueue = maxDemandToQueue,
|
|
AppearancePreference = string.IsNullOrWhiteSpace(appearancePreference)
|
|
? StoryMemoryAppearancePreferences.Balanced
|
|
: appearancePreference.Trim()
|
|
},
|
|
HttpContext.RequestAborted);
|
|
if (rebuild.DryRun)
|
|
{
|
|
TempData["StoryIntelligenceReplayMessage"] = $"Dry run complete. {rebuild.SceneResultsRead:N0} stored scene result(s), {rebuild.SceneResultsProcessed:N0} parseable. No database changes, AI calls, or image credits.";
|
|
}
|
|
else
|
|
{
|
|
TempData["StoryIntelligenceReplayMessage"] = $"Durable Story Memory rebuilt. {rebuild.SceneResultsProcessed:N0} scene result(s) processed, {rebuild.AssignmentsCreatedOrValidated:N0} assignment(s), {rebuild.DemandsCreated:N0} demand record(s), {rebuild.AiCalls:N0} AI calls.";
|
|
}
|
|
|
|
return RedirectToAction(nameof(StoryIntelligenceExperience), new { importSessionId, mode = "replay" });
|
|
}
|
|
|
|
[HttpGet("StoryMemoryDiagnostics")]
|
|
public async Task<IActionResult> StoryMemoryDiagnostics([FromQuery] int importSessionId)
|
|
{
|
|
if (!environment.IsDevelopment())
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (importSessionId <= 0)
|
|
{
|
|
return BadRequest("A valid importSessionId is required.");
|
|
}
|
|
|
|
var diagnostics = await storyMemoryRepository.GetDiagnosticsAsync(importSessionId);
|
|
return Json(diagnostics);
|
|
}
|
|
|
|
[HttpGet("StoryIntelligenceIllustrationDiagnostics")]
|
|
public async Task<IActionResult> StoryIntelligenceIllustrationDiagnostics([FromQuery] int importSessionId)
|
|
{
|
|
if (!environment.IsDevelopment())
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (importSessionId <= 0 || currentUser.UserId is not int userId)
|
|
{
|
|
return BadRequest("A valid importSessionId is required.");
|
|
}
|
|
|
|
var model = await snapshots.BuildImportSessionSnapshotAsync(importSessionId, userId);
|
|
if (model is null)
|
|
{
|
|
return NotFound($"Import Session {importSessionId:N0} could not be found for the current user, or it has no persisted chapter analysis runs yet.");
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
private static StoryIntelligenceExperiencePrototypeViewModel ErrorModel(string requestedMode, int? importSessionId, string message)
|
|
=> new()
|
|
{
|
|
Mode = "error",
|
|
ProjectName = "Development",
|
|
BookTitle = "Story Intelligence",
|
|
SnapshotMessage = message,
|
|
Diagnostics = new StoryIntelligenceExperienceSnapshotDiagnostics
|
|
{
|
|
RequestedMode = requestedMode,
|
|
RequestedImportSessionId = importSessionId,
|
|
SnapshotSource = "Real",
|
|
Warning = message
|
|
}
|
|
};
|
|
}
|