Refine onboarding review wizard steps
This commit is contained in:
parent
bfe729e8c0
commit
54bcd56df4
@ -1275,7 +1275,8 @@ static void ScanReviewAliasesFeedStoryIntelligenceKnownCharacterContext()
|
|||||||
new() { TemporaryCharacterKey = "c-maggie", Name = "Maggie", MentionCount = 8, QualityScore = 94, Category = "ProbableCharacter" },
|
new() { TemporaryCharacterKey = "c-maggie", Name = "Maggie", MentionCount = 8, QualityScore = 94, Category = "ProbableCharacter" },
|
||||||
new() { TemporaryCharacterKey = "c-margaret", Name = "Margaret", MentionCount = 3, QualityScore = 88, Category = "ProbableCharacter" },
|
new() { TemporaryCharacterKey = "c-margaret", Name = "Margaret", MentionCount = 3, QualityScore = 88, Category = "ProbableCharacter" },
|
||||||
new() { TemporaryCharacterKey = "c-mags", Name = "Mags", MentionCount = 2, QualityScore = 78, Category = "PossibleCharacter" },
|
new() { TemporaryCharacterKey = "c-mags", Name = "Mags", MentionCount = 2, QualityScore = 78, Category = "PossibleCharacter" },
|
||||||
new() { TemporaryCharacterKey = "c-kevin", Name = "Kevin", MentionCount = 4, QualityScore = 90, Category = "ProbableCharacter" }
|
new() { TemporaryCharacterKey = "c-kevin", Name = "Kevin", MentionCount = 4, QualityScore = 90, Category = "ProbableCharacter" },
|
||||||
|
new() { TemporaryCharacterKey = "c-road", Name = "Road", MentionCount = 10, QualityScore = 15, Category = "Excluded" }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
var review = new ManuscriptScanReviewDecision
|
var review = new ManuscriptScanReviewDecision
|
||||||
@ -1285,7 +1286,8 @@ static void ScanReviewAliasesFeedStoryIntelligenceKnownCharacterContext()
|
|||||||
new() { TemporaryCharacterKey = "c-maggie", Include = true, Name = "Maggie", Category = "ProbableCharacter", ExistingCharacterID = 123 },
|
new() { TemporaryCharacterKey = "c-maggie", Include = true, Name = "Maggie", Category = "ProbableCharacter", ExistingCharacterID = 123 },
|
||||||
new() { TemporaryCharacterKey = "c-margaret", Include = true, Name = "Margaret", Category = "ProbableCharacter", AliasTargetTemporaryCharacterKey = "c-maggie" },
|
new() { TemporaryCharacterKey = "c-margaret", Include = true, Name = "Margaret", Category = "ProbableCharacter", AliasTargetTemporaryCharacterKey = "c-maggie" },
|
||||||
new() { TemporaryCharacterKey = "c-mags", Include = true, Name = "Mags", Category = "PossibleCharacter", AliasTargetTemporaryCharacterKey = "c-maggie" },
|
new() { TemporaryCharacterKey = "c-mags", Include = true, Name = "Mags", Category = "PossibleCharacter", AliasTargetTemporaryCharacterKey = "c-maggie" },
|
||||||
new() { TemporaryCharacterKey = "c-kevin", Include = true, Name = "Kevin", Category = "ProbableCharacter" }
|
new() { TemporaryCharacterKey = "c-kevin", Include = true, Name = "Kevin", Category = "ProbableCharacter" },
|
||||||
|
new() { TemporaryCharacterKey = "c-road", Include = true, Name = "Road", Category = "Excluded" }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1300,6 +1302,7 @@ static void ScanReviewAliasesFeedStoryIntelligenceKnownCharacterContext()
|
|||||||
Assert(maggie.MentionCount == 13, $"Alias mention counts should accumulate, got {maggie.MentionCount}.");
|
Assert(maggie.MentionCount == 13, $"Alias mention counts should accumulate, got {maggie.MentionCount}.");
|
||||||
Assert(context.Any(item => item.CanonicalName == "Kevin"), "Other included canonical candidates should remain available.");
|
Assert(context.Any(item => item.CanonicalName == "Kevin"), "Other included canonical candidates should remain available.");
|
||||||
Assert(!context.Any(item => item.CanonicalName == "Margaret"), "Alias candidates should not become separate canonical context records.");
|
Assert(!context.Any(item => item.CanonicalName == "Margaret"), "Alias candidates should not become separate canonical context records.");
|
||||||
|
Assert(!context.Any(item => item.CanonicalName == "Road"), "Excluded scan candidates should not guide Story Intelligence.");
|
||||||
}
|
}
|
||||||
|
|
||||||
static JsonSerializerOptions JsonOptions()
|
static JsonSerializerOptions JsonOptions()
|
||||||
|
|||||||
@ -530,7 +530,7 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
|||||||
}
|
}
|
||||||
|
|
||||||
TempData["OnboardingReviewMessage"] = readyToImport
|
TempData["OnboardingReviewMessage"] = readyToImport
|
||||||
? "Review saved. Next: prepare chapters and analyse manuscript."
|
? "Review saved. Next: story and portrait context."
|
||||||
: continueToCharacters ? "Chapter and scene review saved. Next: review detected characters."
|
: continueToCharacters ? "Chapter and scene review saved. Next: review detected characters."
|
||||||
: continueCharacterReview || backCharacterReview ? "Character review choices saved."
|
: continueCharacterReview || backCharacterReview ? "Character review choices saved."
|
||||||
: "Review choices saved.";
|
: "Review choices saved.";
|
||||||
@ -552,13 +552,63 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard
|
|||||||
if (readyToImport || continueCharacterReview || backCharacterReview)
|
if (readyToImport || continueCharacterReview || backCharacterReview)
|
||||||
{
|
{
|
||||||
ViewData["ScanReviewStep"] = "characters";
|
ViewData["ScanReviewStep"] = "characters";
|
||||||
ViewData["CharacterReviewPage"] = Math.Max(1, characterPage);
|
ViewData["CharacterReviewPage"] = readyToImport
|
||||||
|
? FirstInvalidCharacterPage(model)
|
||||||
|
: Math.Max(1, characterPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return View("ScanReview", model);
|
return View("ScanReview", model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int FirstInvalidCharacterPage(ManuscriptScanReviewViewModel model)
|
||||||
|
{
|
||||||
|
const int pageSize = 10;
|
||||||
|
var characters = model.CharacterCandidates
|
||||||
|
.Where(character => !string.Equals(character.Category, "Excluded", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(character => character.ReviewName)
|
||||||
|
.ThenBy(character => character.Name)
|
||||||
|
.ToList();
|
||||||
|
var canonicalNames = characters
|
||||||
|
.Where(IsIncludedCanonicalCharacter)
|
||||||
|
.Select(character => character.ReviewName.Trim())
|
||||||
|
.Where(name => !string.IsNullOrWhiteSpace(name))
|
||||||
|
.GroupBy(name => name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Where(group => group.Count() > 1)
|
||||||
|
.SelectMany(group => group)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
for (var index = 0; index < characters.Count; index++)
|
||||||
|
{
|
||||||
|
var character = characters[index];
|
||||||
|
if (character.Include && string.IsNullOrWhiteSpace(character.ReviewName))
|
||||||
|
{
|
||||||
|
return (index / pageSize) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsIncludedCanonicalCharacter(character) && canonicalNames.Contains(character.ReviewName.Trim()))
|
||||||
|
{
|
||||||
|
return (index / pageSize) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character.Include && !string.IsNullOrWhiteSpace(character.AliasTargetTemporaryCharacterKey))
|
||||||
|
{
|
||||||
|
var target = characters.FirstOrDefault(candidate => string.Equals(candidate.TemporaryCharacterKey, character.AliasTargetTemporaryCharacterKey, StringComparison.Ordinal));
|
||||||
|
if (target is null || !IsIncludedCanonicalCharacter(target))
|
||||||
|
{
|
||||||
|
return (index / pageSize) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsIncludedCanonicalCharacter(ManuscriptScanReviewCharacterViewModel character)
|
||||||
|
=> character.Include
|
||||||
|
&& string.IsNullOrWhiteSpace(character.AliasTargetTemporaryCharacterKey)
|
||||||
|
&& !string.Equals(character.Category, "Excluded", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
[HttpPost("welcome")]
|
[HttpPost("welcome")]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> Welcome()
|
public async Task<IActionResult> Welcome()
|
||||||
|
|||||||
@ -67,9 +67,7 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
var existingBatch = await batchStore.GetLatestForPreviewAsync(userId, preview.PreviewID);
|
var existingBatch = await batchStore.GetLatestForPreviewAsync(userId, preview.PreviewID);
|
||||||
var missingTextCount = included.Count(chapter => string.IsNullOrWhiteSpace(chapter.Preview.ChapterText));
|
var missingTextCount = included.Count(chapter => string.IsNullOrWhiteSpace(chapter.Preview.ChapterText));
|
||||||
var primaryCharacterCount = preview.CharacterCandidates.Count(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase));
|
var primaryCharacterCount = preview.CharacterCandidates.Count(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase));
|
||||||
var excludedCharacterCount = preview.CharacterCandidates.Count(candidate => string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase));
|
var characterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacterCount / 10d));
|
||||||
var characterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacterCount / 10d))
|
|
||||||
+ (excludedCharacterCount > 0 ? (int)Math.Ceiling(excludedCharacterCount / 10d) : 0);
|
|
||||||
var totalWizardSteps = 7 + characterPageCount + 2;
|
var totalWizardSteps = 7 + characterPageCount + 2;
|
||||||
return new StoryIntelligenceOverviewViewModel
|
return new StoryIntelligenceOverviewViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -10,37 +10,47 @@
|
|||||||
var relationshipTitles = Model.CharacterCandidates
|
var relationshipTitles = Model.CharacterCandidates
|
||||||
.Where(candidate => string.Equals(candidate.Category, "RelationshipTitle", StringComparison.OrdinalIgnoreCase))
|
.Where(candidate => string.Equals(candidate.Category, "RelationshipTitle", StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
var excludedCharacters = Model.CharacterCandidates
|
|
||||||
.Where(candidate => string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.ToList();
|
|
||||||
var primaryCharacters = probableCharacters
|
var primaryCharacters = probableCharacters
|
||||||
.Concat(possibleCharacters)
|
.Concat(possibleCharacters)
|
||||||
.Concat(relationshipTitles)
|
.Concat(relationshipTitles)
|
||||||
|
.OrderBy(candidate => candidate.ReviewName)
|
||||||
|
.ThenBy(candidate => candidate.Name)
|
||||||
.ToList();
|
.ToList();
|
||||||
var detectedAliasTargets = Model.CharacterCandidates
|
var detectedAliasTargets = primaryCharacters
|
||||||
.Where(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.OrderBy(candidate => candidate.ReviewName)
|
.OrderBy(candidate => candidate.ReviewName)
|
||||||
.ToList();
|
.ToList();
|
||||||
var visibleCharacterCount = probableCharacters.Count + possibleCharacters.Count + relationshipTitles.Count;
|
var visibleCharacterCount = probableCharacters.Count + possibleCharacters.Count + relationshipTitles.Count;
|
||||||
var isCharacterStep = string.Equals(ViewData["ScanReviewStep"] as string, "characters", StringComparison.OrdinalIgnoreCase);
|
var isCharacterStep = string.Equals(ViewData["ScanReviewStep"] as string, "characters", StringComparison.OrdinalIgnoreCase);
|
||||||
const int characterPageSize = 10;
|
const int characterPageSize = 10;
|
||||||
var primaryCharacterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacters.Count / (double)characterPageSize));
|
var primaryCharacterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacters.Count / (double)characterPageSize));
|
||||||
var excludedCharacterPageCount = excludedCharacters.Any()
|
var characterReviewPageCount = primaryCharacterPageCount;
|
||||||
? (int)Math.Ceiling(excludedCharacters.Count / (double)characterPageSize)
|
|
||||||
: 0;
|
|
||||||
var characterReviewPageCount = primaryCharacterPageCount + excludedCharacterPageCount;
|
|
||||||
var requestedCharacterPage = ViewData["CharacterReviewPage"] is int requestedPage ? requestedPage : 1;
|
var requestedCharacterPage = ViewData["CharacterReviewPage"] is int requestedPage ? requestedPage : 1;
|
||||||
var characterPage = Math.Clamp(requestedCharacterPage, 1, characterReviewPageCount);
|
var characterPage = Math.Clamp(requestedCharacterPage, 1, characterReviewPageCount);
|
||||||
var isExcludedCharacterPage = excludedCharacterPageCount > 0 && characterPage > primaryCharacterPageCount;
|
var currentCharacters = primaryCharacters.Skip((characterPage - 1) * characterPageSize).Take(characterPageSize).ToList();
|
||||||
var excludedPageIndex = isExcludedCharacterPage ? characterPage - primaryCharacterPageCount : 0;
|
|
||||||
var currentCharacters = isExcludedCharacterPage
|
|
||||||
? excludedCharacters.Skip((excludedPageIndex - 1) * characterPageSize).Take(characterPageSize).ToList()
|
|
||||||
: primaryCharacters.Skip((characterPage - 1) * characterPageSize).Take(characterPageSize).ToList();
|
|
||||||
var currentStep = isCharacterStep ? 7 + characterPage : 7;
|
var currentStep = isCharacterStep ? 7 + characterPage : 7;
|
||||||
var totalSteps = 7 + characterReviewPageCount + 2;
|
var totalSteps = 7 + characterReviewPageCount + 2;
|
||||||
var characterPageTitle = isExcludedCharacterPage
|
var characterPageTitle = $"Review Characters {characterPage} of {primaryCharacterPageCount}";
|
||||||
? "Review Excluded Characters"
|
var duplicateCanonicalNames = primaryCharacters
|
||||||
: $"Review Characters {characterPage} of {primaryCharacterPageCount}";
|
.Where(candidate => candidate.Include && string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey))
|
||||||
|
.Select(candidate => candidate.ReviewName.Trim())
|
||||||
|
.Where(name => !string.IsNullOrWhiteSpace(name))
|
||||||
|
.GroupBy(name => name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Where(group => group.Count() > 1)
|
||||||
|
.Select(group => group.Key)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var includedPrimaryKeys = primaryCharacters
|
||||||
|
.Where(candidate => candidate.Include && string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey))
|
||||||
|
.Select(candidate => candidate.TemporaryCharacterKey)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
var invalidCharacterKeys = primaryCharacters
|
||||||
|
.Where(candidate =>
|
||||||
|
(candidate.Include && string.IsNullOrWhiteSpace(candidate.ReviewName))
|
||||||
|
|| (candidate.Include && string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey) && duplicateCanonicalNames.Contains(candidate.ReviewName.Trim()))
|
||||||
|
|| (candidate.Include
|
||||||
|
&& !string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey)
|
||||||
|
&& !includedPrimaryKeys.Contains(candidate.AliasTargetTemporaryCharacterKey)))
|
||||||
|
.Select(candidate => candidate.TemporaryCharacterKey)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
<section class="onboarding-shell" aria-labelledby="scan-review-title">
|
<section class="onboarding-shell" aria-labelledby="scan-review-title">
|
||||||
@ -79,7 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Characters</span>
|
<span>Characters</span>
|
||||||
<strong>@Model.SelectedCharacterCount / @Model.CharacterCandidateCount</strong>
|
<strong>@Model.SelectedCharacterCount / @visibleCharacterCount</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Words</span>
|
<span>Words</span>
|
||||||
@ -114,7 +124,7 @@
|
|||||||
}
|
}
|
||||||
@foreach (var chapter in Model.Chapters)
|
@foreach (var chapter in Model.Chapters)
|
||||||
{
|
{
|
||||||
<details class="onboarding-review-chapter" open>
|
<details class="onboarding-review-chapter">
|
||||||
<summary>
|
<summary>
|
||||||
<span>Chapter @chapter.ChapterNumber</span>
|
<span>Chapter @chapter.ChapterNumber</span>
|
||||||
<strong>@chapter.ReviewTitle</strong>
|
<strong>@chapter.ReviewTitle</strong>
|
||||||
@ -187,9 +197,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>@characterPageTitle</h2>
|
<h2>@characterPageTitle</h2>
|
||||||
<p>
|
<p>
|
||||||
@(isExcludedCharacterPage
|
Character candidates page @characterPage of @primaryCharacterPageCount.
|
||||||
? $"Excluded candidates page {excludedPageIndex} of {excludedCharacterPageCount}."
|
|
||||||
: $"Character candidates page {characterPage} of {primaryCharacterPageCount}.")
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<strong>@currentCharacters.Count character@(currentCharacters.Count == 1 ? string.Empty : "s") on this step</strong>
|
<strong>@currentCharacters.Count character@(currentCharacters.Count == 1 ? string.Empty : "s") on this step</strong>
|
||||||
@ -200,10 +208,6 @@
|
|||||||
<span>Primary candidates</span>
|
<span>Primary candidates</span>
|
||||||
<strong>@visibleCharacterCount.ToString("N0")</strong>
|
<strong>@visibleCharacterCount.ToString("N0")</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<span>Excluded</span>
|
|
||||||
<strong>@excludedCharacters.Count.ToString("N0")</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<span>Wizard pages</span>
|
<span>Wizard pages</span>
|
||||||
<strong>@characterReviewPageCount.ToString("N0")</strong>
|
<strong>@characterReviewPageCount.ToString("N0")</strong>
|
||||||
@ -211,19 +215,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var allCharacters = Model.CharacterCandidates.ToList();
|
var allCharacters = primaryCharacters.ToList();
|
||||||
}
|
}
|
||||||
@if (!currentCharacters.Any())
|
@if (!currentCharacters.Any())
|
||||||
{
|
{
|
||||||
<p class="onboarding-empty-state">No character candidates were found for this step.</p>
|
<p class="onboarding-empty-state">No character candidates were found for this step.</p>
|
||||||
}
|
}
|
||||||
|
@if (invalidCharacterKeys.Any(key => currentCharacters.Any(candidate => string.Equals(candidate.TemporaryCharacterKey, key, StringComparison.Ordinal))))
|
||||||
|
{
|
||||||
|
<p class="onboarding-empty-state onboarding-empty-state--error">The item that needs attention is highlighted below.</p>
|
||||||
|
}
|
||||||
<div class="onboarding-character-groups">
|
<div class="onboarding-character-groups">
|
||||||
@foreach (var group in new[]
|
@foreach (var group in new[]
|
||||||
{
|
{
|
||||||
new { Title = "Probable characters", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "ProbableCharacter", StringComparison.OrdinalIgnoreCase)).ToList() },
|
new { Title = "Probable characters", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "ProbableCharacter", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||||
new { Title = "Possible characters", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "PossibleCharacter", StringComparison.OrdinalIgnoreCase)).ToList() },
|
new { Title = "Possible characters", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "PossibleCharacter", StringComparison.OrdinalIgnoreCase)).ToList() },
|
||||||
new { Title = "Relationship titles", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "RelationshipTitle", StringComparison.OrdinalIgnoreCase)).ToList() },
|
new { Title = "Relationship titles", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "RelationshipTitle", StringComparison.OrdinalIgnoreCase)).ToList() }
|
||||||
new { Title = "Excluded candidates", Candidates = currentCharacters.Where(candidate => string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase)).ToList() }
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
if (!group.Candidates.Any())
|
if (!group.Candidates.Any())
|
||||||
@ -237,7 +244,7 @@
|
|||||||
@foreach (var candidate in group.Candidates)
|
@foreach (var candidate in group.Candidates)
|
||||||
{
|
{
|
||||||
var currentCharacterIndex = allCharacters.FindIndex(item => string.Equals(item.TemporaryCharacterKey, candidate.TemporaryCharacterKey, StringComparison.Ordinal));
|
var currentCharacterIndex = allCharacters.FindIndex(item => string.Equals(item.TemporaryCharacterKey, candidate.TemporaryCharacterKey, StringComparison.Ordinal));
|
||||||
<li>
|
<li class="@(invalidCharacterKeys.Contains(candidate.TemporaryCharacterKey) ? "is-invalid" : string.Empty)">
|
||||||
<input type="hidden" name="Characters[@currentCharacterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
<input type="hidden" name="Characters[@currentCharacterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
||||||
<input type="hidden" name="Characters[@currentCharacterIndex].Category" value="@candidate.Category" />
|
<input type="hidden" name="Characters[@currentCharacterIndex].Category" value="@candidate.Category" />
|
||||||
<label class="onboarding-review-toggle">
|
<label class="onboarding-review-toggle">
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
@model StoryIntelligenceOverviewViewModel
|
@model StoryIntelligenceOverviewViewModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Analyse manuscript";
|
ViewData["Title"] = "Story and portrait context";
|
||||||
}
|
}
|
||||||
|
|
||||||
<section class="onboarding-shell" aria-labelledby="story-intelligence-title">
|
<section class="onboarding-shell" aria-labelledby="story-intelligence-title">
|
||||||
@ -8,8 +8,8 @@
|
|||||||
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = Model.WizardCurrentStep, TotalSteps = Model.WizardTotalSteps })" />
|
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = Model.WizardCurrentStep, TotalSteps = Model.WizardTotalSteps })" />
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Step @Model.WizardCurrentStep of @Model.WizardTotalSteps</p>
|
<p class="eyebrow">Step @Model.WizardCurrentStep of @Model.WizardTotalSteps</p>
|
||||||
<h1 id="story-intelligence-title">Analyse Manuscript</h1>
|
<h1 id="story-intelligence-title">Story and Portrait Context</h1>
|
||||||
<p>Set the analysis context, then PlotDirector will read the approved chapters and prepare scenes, characters, locations, assets, relationships and knowledge for review.</p>
|
<p>Set the context choices for this manuscript before the analysis starts.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (TempData["OnboardingStoryIntelligenceError"] is string error)
|
@if (TempData["OnboardingStoryIntelligenceError"] is string error)
|
||||||
@ -73,8 +73,8 @@
|
|||||||
<form asp-action="StartStoryIntelligence" method="post" class="onboarding-analysis-setup">
|
<form asp-action="StartStoryIntelligence" method="post" class="onboarding-analysis-setup">
|
||||||
<div class="onboarding-analysis-setup__header">
|
<div class="onboarding-analysis-setup__header">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Analysis setup</p>
|
<p class="eyebrow">Pre-analysis setup</p>
|
||||||
<h2>Story and portrait context</h2>
|
<h2>Context choices</h2>
|
||||||
<p>Explicit manuscript evidence always wins. These choices guide unresolved generated portraits and broad story context.</p>
|
<p>Explicit manuscript evidence always wins. These choices guide unresolved generated portraits and broad story context.</p>
|
||||||
</div>
|
</div>
|
||||||
<strong>Required before analysis</strong>
|
<strong>Required before analysis</strong>
|
||||||
@ -144,7 +144,7 @@
|
|||||||
|
|
||||||
<div class="onboarding-actions">
|
<div class="onboarding-actions">
|
||||||
<a class="btn btn-outline-secondary" asp-action="ScanReview" asp-route-previewId="@Model.PreviewID">Back to scan review</a>
|
<a class="btn btn-outline-secondary" asp-action="ScanReview" asp-route-previewId="@Model.PreviewID">Back to scan review</a>
|
||||||
<button class="btn btn-primary" type="submit" disabled="@(!Model.CanStart)">Analyse manuscript</button>
|
<button class="btn btn-primary" type="submit" disabled="@(!Model.CanStart)">Save context and analyse manuscript</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
new("Connect Word", 6),
|
new("Connect Word", 6),
|
||||||
new("Review Chapters", 7),
|
new("Review Chapters", 7),
|
||||||
new("Review Characters", 8),
|
new("Review Characters", 8),
|
||||||
new("Analyse Manuscript", 9),
|
new("Story Context", 9),
|
||||||
new("Complete", 10)
|
new("Complete", 10)
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -60,7 +60,7 @@
|
|||||||
steps.Add(new HeaderStep($"Characters {index}", 7 + index));
|
steps.Add(new HeaderStep($"Characters {index}", 7 + index));
|
||||||
}
|
}
|
||||||
|
|
||||||
steps.Add(new HeaderStep("Analyse Manuscript", totalSteps - 1));
|
steps.Add(new HeaderStep("Story Context", totalSteps - 1));
|
||||||
steps.Add(new HeaderStep("Complete", totalSteps));
|
steps.Add(new HeaderStep("Complete", totalSteps));
|
||||||
return steps;
|
return steps;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -530,6 +530,12 @@
|
|||||||
background: rgba(255, 255, 255, .55);
|
background: rgba(255, 255, 255, .55);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-empty-state--error {
|
||||||
|
border-color: rgba(176, 54, 54, .38);
|
||||||
|
color: #8f2f2f;
|
||||||
|
background: rgba(176, 54, 54, .08);
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-review-chapters {
|
.onboarding-review-chapters {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: .85rem;
|
gap: .85rem;
|
||||||
@ -574,6 +580,12 @@
|
|||||||
background: rgba(255, 255, 255, .64);
|
background: rgba(255, 255, 255, .64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-character-candidates li.is-invalid {
|
||||||
|
border-color: rgba(176, 54, 54, .48);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(176, 54, 54, .16);
|
||||||
|
background: rgba(176, 54, 54, .06);
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-review-chapter li {
|
.onboarding-review-chapter li {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: .45rem;
|
gap: .45rem;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user