@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); }

Analyse manuscript

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
}
PlotDirector will keep analysing your manuscript. You can close Word, close this browser tab, and return later. We’ll show this import on your Projects page while it is running.

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) { Continue in background } @if (!Model.HasActiveRuns && Model.Chapters.Any(chapter => chapter.IsRunComplete || chapter.IsFailed || chapter.HasCompletedCommit)) { Review scenes } }
    @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); } }