PlotDirector/PlotLine/Views/Onboarding/StoryIntelligenceNarrative.cshtml

235 lines
16 KiB
Plaintext

@model StoryIntelligenceProgressViewModel
@{
ViewData["Title"] = "Review Plot Lines & Threads";
const int previewLimit = 25;
var narrativeCandidates = Model.NarrativeReview.Candidates.Take(previewLimit).ToList();
var hiddenCandidateCount = Math.Max(0, Model.NarrativeReview.Candidates.Count - narrativeCandidates.Count);
var eventTypes = new[]
{
"Introduced", "Mentioned", "Developed", "Complicated", "Clue Planted", "Question Raised", "Question Answered",
"False Lead", "Revealed", "Payoff", "Resolved", "Contradicted", "Retconned", "Note"
};
}
<section class="onboarding-shell" aria-labelledby="story-narrative-title">
<div class="onboarding-panel onboarding-review-panel story-review-page">
<div class="onboarding-copy story-review-heading">
<p class="eyebrow">Story Intelligence</p>
<h1 id="story-narrative-title">Review Plot Lines & Threads</h1>
<p>Review recurring narrative arcs and thread candidates from the discovered hierarchy.</p>
</div>
@if (TempData["OnboardingStoryIntelligenceError"] is string error)
{
<div class="alert alert-danger">@error</div>
}
@if (TempData["OnboardingStoryIntelligenceMessage"] is string message)
{
<div class="alert alert-success">@message</div>
}
@if (Model.NarrativeReview.Candidates.Count == 0)
{
<div class="story-review-note">
<strong>No Plot Line or Thread candidates are waiting.</strong>
<p class="mb-0">PlotDirector did not find recurring narrative candidates that require approval, or this review has already been completed.</p>
</div>
<form asp-action="ImportStoryIntelligenceNarrative" method="post" class="onboarding-actions" data-story-review-form>
<input type="hidden" name="BatchID" value="@Model.BatchID" />
<a class="btn btn-outline-secondary" asp-action="StoryIntelligenceReviewCentre" asp-route-batchId="@Model.BatchID">Back to Review Centre</a>
<button class="btn btn-primary" type="submit" data-busy-label="Saving plot lines...">Mark reviewed</button>
</form>
}
else
{
<form asp-action="ImportStoryIntelligenceNarrative" method="post" data-story-review-form>
<input type="hidden" name="BatchID" value="@Model.BatchID" />
@if (hiddenCandidateCount > 0)
{
<div class="story-review-card-actions">
<span class="story-review-page-status">Showing @narrativeCandidates.Count.ToString("N0") of @Model.NarrativeReview.Candidates.Count.ToString("N0")</span>
</div>
}
<div class="story-character-card-grid">
@for (var i = 0; i < narrativeCandidates.Count; i++)
{
var candidate = narrativeCandidates[i];
<details class="story-character-card" open>
<summary class="story-review-card-header">
<span>
<strong>@candidate.ProposedTitle</strong>
<small>@candidate.CandidateType @(!string.IsNullOrWhiteSpace(candidate.Category) ? $"- {candidate.Category}" : string.Empty)</small>
</span>
<em>@candidate.Confidence</em>
</summary>
<input type="hidden" name="Candidates[@i].NarrativeCandidateID" value="@candidate.NarrativeCandidateID" />
<input type="hidden" name="Candidates[@i].CandidateType" value="@candidate.CandidateType" />
<div class="story-character-card__body">
<div>
<label>Title</label>
<input class="form-control" name="Candidates[@i].Title" value="@candidate.ProposedTitle" maxlength="200" />
</div>
<div>
<label>Description</label>
<textarea class="form-control" name="Candidates[@i].Description" rows="3">@candidate.ProposedDescription</textarea>
</div>
@if (!string.IsNullOrWhiteSpace(candidate.AuthorPlotSeedDescription))
{
<div class="story-review-note">
<strong>Author provided</strong>
<p>@candidate.AuthorPlotSeedDescription</p>
@if (!string.IsNullOrWhiteSpace(candidate.SeedMatchReason))
{
<p class="text-muted mb-0">@candidate.SeedMatchReason</p>
}
</div>
}
else if (string.Equals(candidate.Source, WholeBookPlotSynthesisSources.WholeBook, StringComparison.OrdinalIgnoreCase))
{
<p class="text-muted">Discovered by PlotDirector</p>
}
@if (candidate.ExistingPlotLineID.HasValue && !string.IsNullOrWhiteSpace(candidate.ExistingPlotLineName))
{
<p class="text-muted">Existing Plot Line: @candidate.ExistingPlotLineName</p>
}
@if (string.Equals(candidate.CandidateType, StoryIntelligenceNarrativeCandidateTypes.Thread, StringComparison.OrdinalIgnoreCase))
{
@if (!string.IsNullOrWhiteSpace(candidate.ParentCandidateTitle))
{
<p class="text-muted">Proposed parent: @candidate.ParentCandidateTitle</p>
}
<div>
<label>Existing related Plot Line</label>
<select class="form-select" name="Candidates[@i].RelatedPlotLineID">
<option value="">Choose Plot Line</option>
@foreach (var option in Model.NarrativeReview.PlotLineOptions)
{
<option value="@option.Value" selected="@(candidate.RelatedPlotLineID?.ToString() == option.Value)">@option.Text</option>
}
</select>
</div>
<div>
<label>Proposed parent Plot Line</label>
<select class="form-select" name="Candidates[@i].ParentNarrativeCandidateID">
<option value="">No proposed parent</option>
@foreach (var option in Model.NarrativeReview.CandidatePlotLineOptions)
{
<option value="@option.Value" selected="@(candidate.ParentNarrativeCandidateID?.ToString() == option.Value)">@option.Text</option>
}
</select>
</div>
}
<fieldset>
<legend>Decision</legend>
<label><input type="radio" name="Candidates[@i].Action" value="@StoryIntelligenceNarrativeReviewActions.AcceptNew" checked="@(candidate.DefaultAction == StoryIntelligenceNarrativeReviewActions.AcceptNew)" /> Accept new</label>
<label><input type="radio" name="Candidates[@i].Action" value="@StoryIntelligenceNarrativeReviewActions.MergeExisting" checked="@(candidate.DefaultAction == StoryIntelligenceNarrativeReviewActions.MergeExisting)" /> Merge with existing</label>
<label><input type="radio" name="Candidates[@i].Action" value="@StoryIntelligenceNarrativeReviewActions.MergeCandidate" /> Merge with candidate</label>
<label><input type="radio" name="Candidates[@i].Action" value="@StoryIntelligenceNarrativeReviewActions.Reject" /> Reject</label>
</fieldset>
<div>
<label>Existing Plot Line for merge</label>
<select class="form-select" name="Candidates[@i].ExistingPlotLineID">
<option value="">Choose existing Plot Line</option>
@foreach (var option in Model.NarrativeReview.PlotLineOptions)
{
<option value="@option.Value" selected="@(candidate.ExistingPlotLineID?.ToString() == option.Value)">@option.Text</option>
}
</select>
</div>
<div>
<label>Candidate for merge</label>
<select class="form-select" name="Candidates[@i].MergeTargetNarrativeCandidateID">
<option value="">Choose proposed candidate</option>
@foreach (var option in narrativeCandidates.Where(item => item.NarrativeCandidateID != candidate.NarrativeCandidateID && string.Equals(item.CandidateType, candidate.CandidateType, StringComparison.OrdinalIgnoreCase)).Select(item => new SelectListItem(item.ProposedTitle, item.NarrativeCandidateID.ToString())))
{
<option value="@option.Value">@option.Text</option>
}
</select>
</div>
@if (candidate.ChildThreads.Count > 0)
{
<div class="story-review-note">
<strong>Suggested child Threads</strong>
<ul>
@foreach (var child in candidate.ChildThreads)
{
<li>@child.ProposedTitle</li>
}
</ul>
</div>
}
@if (candidate.Events.Count > 0)
{
<div class="story-review-note">
<strong>Proposed timeline events</strong>
@for (var e = 0; e < candidate.Events.Count; e++)
{
var item = candidate.Events[e];
<div class="story-review-event">
<input type="hidden" name="Candidates[@i].Events[@e].NarrativeCandidateEventID" value="@item.NarrativeCandidateEventID" />
<input type="hidden" name="Candidates[@i].Events[@e].Include" value="false" />
<label class="story-review-event__include">
<input type="checkbox" name="Candidates[@i].Events[@e].Include" value="true" checked />
<span>@item.SceneLabel</span>
</label>
<div class="row g-2">
<div class="col-md-4">
<label>Event type</label>
<select class="form-select" name="Candidates[@i].Events[@e].EventType">
@foreach (var eventType in eventTypes)
{
<option value="@eventType" selected="@(string.Equals(item.EventType, eventType, StringComparison.OrdinalIgnoreCase))">@eventType</option>
}
</select>
</div>
<div class="col-md-8">
<label>Title</label>
<input class="form-control" name="Candidates[@i].Events[@e].EventTitle" value="@item.EventTitle" maxlength="200" />
</div>
<div class="col-12">
<label>Description</label>
<textarea class="form-control" name="Candidates[@i].Events[@e].EventDescription" rows="2">@item.EventDescription</textarea>
</div>
</div>
@if (!string.IsNullOrWhiteSpace(item.Evidence))
{
<p class="text-muted mb-0">@item.Evidence</p>
}
</div>
}
</div>
}
else
{
<div class="story-review-note">
<strong>Supporting scenes</strong>
<ul>
@foreach (var evidence in candidate.Evidence)
{
<li><strong>@evidence.SceneLabel</strong>: @(string.IsNullOrWhiteSpace(evidence.Evidence) ? evidence.ContributionType : evidence.Evidence)</li>
}
</ul>
</div>
}
</div>
</details>
}
</div>
<div class="onboarding-actions">
@if (hiddenCandidateCount > 0)
{
<span class="text-muted">Save this batch to continue with @hiddenCandidateCount.ToString("N0") more narrative decision@(hiddenCandidateCount == 1 ? string.Empty : "s").</span>
}
<a class="btn btn-outline-secondary" asp-action="StoryIntelligenceReviewCentre" asp-route-batchId="@Model.BatchID">Back to Review Centre</a>
<button class="btn btn-primary" type="submit" data-busy-label="Saving plot lines...">Save Plot Lines & Threads</button>
</div>
</form>
}
</div>
</section>
@section Scripts {
<script src="~/js/story-review-actions.js" asp-append-version="true"></script>
}