@model StoryIntelligenceProgressViewModel @using System.Security.Claims @inject IConfiguration Configuration @{ ViewData["Title"] = "Review analysis"; var adminEmails = Configuration.GetSection("Admin:AllowedEmails").Get() ?? []; var userEmail = User.FindFirstValue(ClaimTypes.Email); var isAdmin = !string.IsNullOrWhiteSpace(userEmail) && adminEmails.Any(email => string.Equals(email, userEmail, StringComparison.OrdinalIgnoreCase)); var chaptersNeedingReview = Model.Chapters.Count(NeedsReview); var warningsCount = Model.Chapters.Sum(chapter => NeedsReviewCount(chapter)); }

Step 9 of 9

Review Story Intelligence Results

PlotDirector has finished reading your manuscript. Nothing has been added to your project yet. Please review the detected scenes before creating them.

@if (TempData["OnboardingStoryIntelligenceError"] is string error) {
@error
} @if (TempData["OnboardingStoryIntelligenceMessage"] is string message) {
@message
}
Only scenes will be created. Characters, locations, assets, relationships and knowledge will be imported during later Story Intelligence phases.
Story Intelligence complete @Model.CompletedChapterCount.ToString("N0") chapter@(Model.CompletedChapterCount == 1 ? string.Empty : "s")
Scenes detected @Model.TotalDetectedScenes.ToString("N0")
Words analysed @Model.TotalAnalysedWords.ToString("N0")
Elapsed time @FormatDuration(Model.TotalDurationMs)
Estimated AI cost @FormatCost(Model.TotalEstimatedCostUSD)
Needs review @warningsCount.ToString("N0")
@foreach (var chapter in Model.Chapters.OrderBy(chapter => chapter.ChapterNumber)) {

Chapter @chapter.ChapterNumber

@chapter.ChapterTitle

@DetectedCount(chapter).ToString("N0") scene@(DetectedCount(chapter) == 1 ? string.Empty : "s") detected / @ReadyCount(chapter).ToString("N0") ready to create

@FriendlyStatus(chapter)
Detected scenes @DetectedCount(chapter).ToString("N0")
Ready @ReadyCount(chapter).ToString("N0")
Needs review @NeedsReviewCount(chapter).ToString("N0")
Rejected @RejectedCount(chapter).ToString("N0")
@if (!string.IsNullOrWhiteSpace(chapter.CommitMessage)) {

@chapter.CommitMessage

} @if (chapter.Warnings.Count > 0) {
Notes
    @foreach (var warning in chapter.Warnings) {
  • @AuthorMessage(warning)
  • }
} @if (chapter.Blockers.Count > 0) {
Before scenes can be created
    @foreach (var blocker in chapter.Blockers) {
  • @AuthorMessage(blocker)
  • }
} @if (!string.IsNullOrWhiteSpace(chapter.ErrorMessage)) {

@AuthorMessage(chapter.ErrorMessage)

}
@if (chapter.Scenes.Count == 0) {

No readable scene previews are available for this chapter.

} else { @foreach (var scene in chapter.Scenes) {
Scene @scene.SceneNumber.ToString("N0") @if (scene.HasWarnings) { Review recommended }

@scene.Summary

POV
@Display(scene.Pov)
Setting
@Display(scene.Setting)
Confidence
@FormatConfidence(scene.Confidence)
} }
@if (isAdmin) {
Admin diagnostics
Run ID
@chapter.RunID
Status
@chapter.Status
Stage
@(chapter.CurrentStage ?? "-")
Failure stage
@(chapter.FailureStage ?? "-")
Tokens
@(chapter.TotalTokens?.ToString("N0") ?? "-")
Open diagnostics
}
}
Scene creation uses PlotDirector import safeguards. Duplicate imports are blocked, and failures roll back safely.
@if (Model.BatchID.HasValue) { Back @if (Model.HasReadyScenesToCreate) {
} else if (Model.AllCommitted) { Continue } }
@functions { private static bool NeedsReview(StoryIntelligenceOnboardingChapterViewModel chapter) => chapter.IsFailed || chapter.Blockers.Count > 0 || NeedsReviewCount(chapter) > 0 || (!chapter.CanCommit && !chapter.HasCompletedCommit && chapter.IsRunComplete); private static int DetectedCount(StoryIntelligenceOnboardingChapterViewModel chapter) => Math.Max(chapter.TotalDetectedScenes ?? 0, ReadyCount(chapter) + Math.Max(chapter.FailedScenes ?? 0, 0)); private static int ReadyCount(StoryIntelligenceOnboardingChapterViewModel chapter) => chapter.HasCompletedCommit ? chapter.ScenesCreated : chapter.Scenes.Count; private static int RejectedCount(StoryIntelligenceOnboardingChapterViewModel chapter) => 0; private static int NeedsReviewCount(StoryIntelligenceOnboardingChapterViewModel chapter) => Math.Max(0, DetectedCount(chapter) - ReadyCount(chapter) - RejectedCount(chapter)); private static string FriendlyStatus(StoryIntelligenceOnboardingChapterViewModel chapter) { if (chapter.HasCompletedCommit) { return "Scenes created"; } if (chapter.CanCommit) { return "Ready"; } return chapter.Status switch { StoryIntelligenceRunStatuses.Pending => "Waiting", StoryIntelligenceRunStatuses.Running => "Reading", StoryIntelligenceRunStatuses.Completed => "Cannot import", StoryIntelligenceRunStatuses.CompletedWithWarnings => "Cannot import", StoryIntelligenceRunStatuses.Failed => "Needs review", StoryIntelligenceRunStatuses.Cancelled => "Cancelled", _ => "Needs review" }; } private static string StatusClass(StoryIntelligenceOnboardingChapterViewModel chapter) { if (chapter.HasCompletedCommit) { return "story-review-status story-review-status--created"; } if (chapter.CanCommit) { return "story-review-status story-review-status--ready"; } return chapter.IsActive ? "story-review-status story-review-status--active" : "story-review-status story-review-status--warning"; } private static string Display(string? value) => string.IsNullOrWhiteSpace(value) ? "Not detected" : value; private static string AuthorMessage(string? value) { if (string.IsNullOrWhiteSpace(value)) { return "This chapter needs review before scenes can be created."; } var message = value.Trim(); if (message.Contains("JSON", StringComparison.OrdinalIgnoreCase) || message.Contains("parsed", StringComparison.OrdinalIgnoreCase) || message.Contains("parser", StringComparison.OrdinalIgnoreCase) || message.Contains("deserial", StringComparison.OrdinalIgnoreCase)) { return "One or more scene suggestions could not be prepared automatically and need review."; } if (message.Contains("existing authored scenes", StringComparison.OrdinalIgnoreCase) || message.Contains("already contains scenes", StringComparison.OrdinalIgnoreCase)) { return "This chapter already contains scenes. Choose an empty chapter before creating scenes from this analysis."; } return message; } private static string FormatConfidence(decimal? confidence) { if (!confidence.HasValue) { return "Not available"; } var value = Math.Clamp(confidence.Value, 0m, 1m); return value >= 0.75m ? "High" : value >= 0.45m ? "Medium" : "Low"; } private static string FormatCost(decimal? cost) => cost.HasValue ? $"About ${cost.Value:0.00}" : "Not available"; private static string FormatDuration(long milliseconds) { var elapsed = TimeSpan.FromMilliseconds(Math.Max(0, milliseconds)); if (elapsed.TotalHours >= 1) { return $"{Math.Ceiling(elapsed.TotalHours):N0} hr"; } return elapsed.TotalMinutes >= 1 ? $"{Math.Max(1, (int)Math.Round(elapsed.TotalMinutes)):N0} min" : $"{Math.Max(0, (int)Math.Round(elapsed.TotalSeconds)):N0} sec"; } }