219 lines
15 KiB
Plaintext
219 lines
15 KiB
Plaintext
@model StoryIntelligenceProgressViewModel
|
|
@{
|
|
ViewData["Title"] = "Review Plot Lines & Threads";
|
|
const int previewLimit = 50;
|
|
var showAll = string.Equals(Context.Request.Query["view"], "all", StringComparison.OrdinalIgnoreCase);
|
|
var narrativeCandidates = showAll
|
|
? Model.NarrativeReview.Candidates.ToList()
|
|
: 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">
|
|
<a class="btn btn-outline-secondary btn-sm" asp-action="StoryIntelligenceNarrative" asp-route-batchId="@Model.BatchID" asp-route-view="all">View all @Model.NarrativeReview.Candidates.Count.ToString("N0")</a>
|
|
</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>
|
|
<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.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 Model.NarrativeReview.Candidates.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">@hiddenCandidateCount.ToString("N0") more narrative decision@(hiddenCandidateCount == 1 ? string.Empty : "s") available after this batch.</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>
|
|
}
|