@model StoryIntelligenceProgressViewModel @{ ViewData["Title"] = "Review scenes"; var runIds = string.Join(",", Model.Chapters.Select(chapter => chapter.RunID)); var progressPercent = ProgressPercent(Model); }

@Model.BookTitle

Review scenes

PlotDirector is reading the approved chapters and turning them into scenes you can review before creating them.

@if (TempData["OnboardingStoryIntelligenceError"] is string error) {
@error
} @if (TempData["OnboardingStoryIntelligenceMessage"] is string message) {
@message
}
Overall progress @progressPercent%
Chapters completed @Model.CompletedChapterCount / @Model.ChapterCount
Scenes analysed @Model.TotalCompletedScenes / @Model.TotalDetectedScenes
Failures @(Model.FailedChapterCount + Model.TotalFailedScenes)

Reading now

Current chapter @(CurrentChapter(Model)?.ChapterTitle ?? "Waiting")
Current stage @(CurrentChapter(Model)?.CurrentStage ?? "Queued")
Elapsed @Elapsed(Model)
Estimated remaining Calculating

@(CurrentChapter(Model)?.CurrentMessage ?? "Waiting for analysis to begin.")

    Chapters

    @foreach (var chapter in Model.Chapters.OrderBy(chapter => chapter.ChapterNumber)) {
    @chapter.ChapterNumber. @chapter.ChapterTitle @ChapterStatusLabel(chapter)
    Status @chapter.Status
    Scenes @((chapter.CompletedScenes ?? 0).ToString("N0")) / @((chapter.TotalDetectedScenes ?? 0).ToString("N0"))
    Failed @((chapter.FailedScenes ?? 0).ToString("N0"))
    Tokens @(chapter.TotalTokens?.ToString("N0") ?? "-")

    @(chapter.CurrentMessage ?? string.Empty)

    @if (!string.IsNullOrWhiteSpace(chapter.ErrorMessage)) {

    @chapter.ErrorMessage

    } else { } @if (chapter.Warnings.Any()) {
      @foreach (var warning in chapter.Warnings) {
    • @warning
    • }
    } @if (chapter.Blockers.Any()) {
      @foreach (var blocker in chapter.Blockers) {
    • @blocker
    • }
    }
    @if (chapter.HasCompletedCommit) { @chapter.ScenesCreated scene(s) created } else if (chapter.CanCommit && Model.BatchID.HasValue) {
    } else if (chapter.IsRunComplete) { Review needed }
    }
    @if (Model.BatchID.HasValue) {
    Continue }
    @section Scripts { } @functions { private static string ChapterStatusLabel(StoryIntelligenceOnboardingChapterViewModel chapter) { if (chapter.HasCompletedCommit) { return "Scenes created"; } if (chapter.CanCommit) { return "Ready to create"; } if (chapter.IsActive) { return "Analysing"; } if (chapter.IsFailed) { return "Needs attention"; } return "Reviewing"; } private static StoryIntelligenceOnboardingChapterViewModel? CurrentChapter(StoryIntelligenceProgressViewModel model) => model.Chapters.FirstOrDefault(chapter => chapter.IsActive) ?? model.Chapters.LastOrDefault(chapter => chapter.IsRunComplete) ?? model.Chapters.FirstOrDefault(); private static int ProgressPercent(StoryIntelligenceProgressViewModel model) { if (model.ChapterCount == 0) { return 0; } if (model.TotalDetectedScenes > 0) { return Math.Clamp((int)Math.Round(model.TotalCompletedScenes * 100m / Math.Max(1, model.TotalDetectedScenes)), 0, 100); } return Math.Clamp((int)Math.Round(model.CompletedChapterCount * 100m / Math.Max(1, model.ChapterCount)), 0, 100); } private static string Elapsed(StoryIntelligenceProgressViewModel model) { var elapsedMs = model.Chapters.Max(chapter => chapter.TotalDurationMs ?? 0); var elapsed = TimeSpan.FromMilliseconds(Math.Max(0, elapsedMs)); return elapsed.TotalMinutes >= 1 ? $"{Math.Max(1, (int)Math.Round(elapsed.TotalMinutes)):N0} min" : $"{Math.Max(0, (int)Math.Round(elapsed.TotalSeconds)):N0} sec"; } }