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

Step 8 of 9

Reading your manuscript

PlotDirector is analysing your chapters and preparing scenes for review. You can safely leave this page and return later.

@if (TempData["OnboardingStoryIntelligenceError"] is string error) {
@error
} @if (TempData["OnboardingStoryIntelligenceMessage"] is string message) {
@message
}
You can close Word or this browser tab once analysis has started. PlotDirector is working from a saved manuscript snapshot.

Import progress

@progressPercent%
@FriendlyStage(currentChapter?.CurrentStage, currentChapter?.Status)
Chapters read @Model.CompletedChapterCount of @Model.ChapterCount
Scenes read @Model.TotalCompletedScenes so far
Needs review @(Model.FailedChapterCount + Model.TotalFailedScenes)
Elapsed @Elapsed(Model)
Estimated remaining @EstimateRemaining(Model)

Current chapter

@(currentChapter?.ChapterTitle ?? "Waiting")

@FriendlyMessage(currentChapter)

Current scene

@CurrentScene(Model)
@ChapterProgressText(currentChapter)

Latest from your manuscript

@if (!string.IsNullOrWhiteSpace(currentChapter?.LatestSceneSummary)) {

@currentChapter.LatestSceneSummary

} else {

Scene summaries will appear here as PlotDirector reads.

}

Recent discoveries

Possible characters, locations and story clues will appear here as PlotDirector reads.

    @if (Model.BatchID.HasValue) {
    @if (!Model.HasActiveRuns && Model.Chapters.Any(chapter => chapter.IsRunComplete || chapter.IsFailed || chapter.HasCompletedCommit)) { Review analysis } }
    @section Scripts { } @functions { private static string FriendlyStatus(string? status) => status switch { StoryIntelligenceRunStatuses.Pending => "Waiting", StoryIntelligenceRunStatuses.Running => "Reading", StoryIntelligenceRunStatuses.Completed => "Ready to create", StoryIntelligenceRunStatuses.CompletedWithWarnings => "Ready", StoryIntelligenceRunStatuses.Failed => "Needs review", StoryIntelligenceRunStatuses.Cancelled => "Cancelled", _ => "Waiting" }; private static string FriendlyStage(string? stage, string? status) { if (status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings) { return "Ready to review"; } if (status is StoryIntelligenceRunStatuses.Failed) { return "Needs review"; } if (status is StoryIntelligenceRunStatuses.Cancelled) { return "Cancelled"; } return stage switch { "Queued" => "Waiting", StoryIntelligenceFailureStages.ChapterStructure => "Finding scenes", StoryIntelligenceFailureStages.SceneIntelligence => "Reading scenes", StoryIntelligenceFailureStages.SceneSplit => "Preparing review", StoryIntelligenceFailureStages.Validation => "Preparing review", StoryIntelligenceFailureStages.Persistence => "Preparing review", _ => "Reading" }; } private static string FriendlyMessage(StoryIntelligenceOnboardingChapterViewModel? chapter) { if (chapter is null) { return "Waiting for analysis to begin."; } if (chapter.Status == StoryIntelligenceRunStatuses.Cancelled) { return "Analysis was cancelled. Any completed chapters remain available for review."; } if (chapter.Status == StoryIntelligenceRunStatuses.Failed) { return "This chapter needs review before scenes can be created."; } return chapter.CurrentStage switch { "Queued" => "Waiting to read this chapter.", StoryIntelligenceFailureStages.ChapterStructure => "Finding scenes in this chapter.", StoryIntelligenceFailureStages.SceneSplit => "Preparing the detected scenes.", StoryIntelligenceFailureStages.SceneIntelligence => "Reading the detected scenes.", StoryIntelligenceFailureStages.Validation => "Checking the results.", _ => chapter.IsRunComplete ? "This chapter is ready to review." : "Waiting for analysis to begin." }; } 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; } var chapterProgress = model.Chapters.Sum(ChapterProgress); return Math.Clamp((int)Math.Floor(chapterProgress * 100m / model.ChapterCount), 0, 100); } private static decimal ChapterProgress(StoryIntelligenceOnboardingChapterViewModel chapter) { if (chapter.HasCompletedCommit || chapter.IsRunComplete || chapter.IsFailed) { return 1m; } if (chapter.Status == StoryIntelligenceRunStatuses.Pending) { return 0m; } if ((chapter.TotalDetectedScenes ?? 0) > 0) { var sceneProgress = Math.Clamp((chapter.CompletedScenes ?? 0) / Math.Max(1m, chapter.TotalDetectedScenes ?? 1), 0m, 1m); return Math.Clamp(0.25m + (sceneProgress * 0.7m), 0m, 0.95m); } return 0.15m; } 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"; } private static string EstimateRemaining(StoryIntelligenceProgressViewModel model) { if (!model.HasActiveRuns) { return "Ready to review"; } var completed = model.Chapters .Where(chapter => chapter.IsRunComplete && (chapter.SourceWordCount ?? 0) > 0 && (chapter.TotalDurationMs ?? 0) > 0) .ToList(); if (completed.Count == 0) { return "Estimating..."; } var completedWords = completed.Sum(chapter => chapter.SourceWordCount ?? 0); var completedSeconds = completed.Sum(chapter => chapter.TotalDurationMs ?? 0) / 1000m; var remainingWords = model.Chapters .Where(chapter => !chapter.IsRunComplete && !chapter.IsFailed) .Sum(chapter => chapter.SourceWordCount ?? 0); if (completedWords <= 0 || completedSeconds <= 0 || remainingWords <= 0) { return "Estimating..."; } var secondsPerWord = completedSeconds / completedWords; var bufferedSeconds = secondsPerWord * remainingWords * 1.35m; return FriendlyRemaining(bufferedSeconds); } private static string FriendlyRemaining(decimal seconds) { var minutes = (int)Math.Ceiling(seconds / 60m); if (minutes <= 0) { return "Estimating..."; } if (minutes <= 20) { return "About 20-30 minutes"; } if (minutes <= 45) { return "About 45-60 minutes"; } if (minutes <= 120) { return "About 1-2 hours"; } return "More than 2 hours"; } private static string CurrentScene(StoryIntelligenceProgressViewModel model) { var chapter = CurrentChapter(model); return chapter?.CompletedScenes is > 0 ? chapter.CompletedScenes.Value.ToString("N0") : "Waiting"; } private static string ChapterProgressText(StoryIntelligenceOnboardingChapterViewModel? chapter) { if ((chapter?.TotalDetectedScenes ?? 0) <= 0) { return "Finding scenes..."; } var completed = Math.Clamp(chapter?.CompletedScenes ?? 0, 0, chapter?.TotalDetectedScenes ?? 0); return $"Scene {completed:N0} of {chapter?.TotalDetectedScenes:N0}"; } private static int ChapterProgressPercent(StoryIntelligenceOnboardingChapterViewModel? chapter) { var total = chapter?.TotalDetectedScenes ?? 0; if (total <= 0) { return 0; } var completed = Math.Clamp(chapter?.CompletedScenes ?? 0, 0, total); return Math.Clamp((int)Math.Floor(completed * 100m / total), 0, 100); } }