249 lines
11 KiB
Plaintext
249 lines
11 KiB
Plaintext
@model ChapterStructureDryRunViewModel
|
|
@{
|
|
ViewData["Title"] = "Chapter Intelligence Test";
|
|
var result = Model.Result;
|
|
}
|
|
|
|
<div class="page-heading compact">
|
|
<div>
|
|
<p class="eyebrow">Admin utility</p>
|
|
<h1>Chapter Intelligence Test</h1>
|
|
<p class="lead-text">This is a structural dry run. It makes one live OpenAI request only when submitted, and it does not create scenes or save data.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="mb-3" aria-label="Breadcrumb">
|
|
<a asp-controller="Projects" asp-action="Index">Projects</a>
|
|
<span class="muted"> / </span>
|
|
<a asp-action="Index">Admin</a>
|
|
<span class="muted"> / </span>
|
|
<a asp-action="StoryIntelligenceDiagnostics">Story Intelligence Diagnostics</a>
|
|
<span class="muted"> / Chapter Intelligence Test</span>
|
|
</nav>
|
|
|
|
<section class="edit-panel">
|
|
<h2>Manual chapter structure test</h2>
|
|
<p>Use fictional sample text only. This page loads <code>Chapter-Structure-Prompt-V2.md</code>, replaces runtime placeholders, asks for likely scene boundaries, and displays the raw response without saving it.</p>
|
|
<form asp-action="ChapterStructureTest" method="post">
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label" asp-for="Form.ProjectID">ProjectID</label>
|
|
<input class="form-control" asp-for="Form.ProjectID" />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label" asp-for="Form.BookID">BookID</label>
|
|
<input class="form-control" asp-for="Form.BookID" />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label" asp-for="Form.ChapterID">ChapterID</label>
|
|
<input class="form-control" asp-for="Form.ChapterID" />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label" asp-for="Form.ChapterNumber">ChapterNumber</label>
|
|
<input class="form-control" asp-for="Form.ChapterNumber" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label" asp-for="Form.SourceLabel">SourceLabel</label>
|
|
<input class="form-control" asp-for="Form.SourceLabel" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label" asp-for="Form.ChapterText">Chapter text</label>
|
|
<textarea class="form-control" asp-for="Form.ChapterText" rows="14"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="button-row mt-3">
|
|
<button class="btn btn-primary" type="submit">Run chapter test</button>
|
|
<a class="btn btn-outline-secondary" asp-action="StoryIntelligenceDiagnostics">Back to diagnostics</a>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
|
|
@if (result is not null)
|
|
{
|
|
<section class="edit-panel">
|
|
<h2>Chapter structure result</h2>
|
|
@if (!result.Success)
|
|
{
|
|
<div class="alert alert-warning">@result.ErrorMessage</div>
|
|
}
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Prompt version</p>
|
|
<h3 class="mb-0">@result.PromptVersion</h3>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Model</p>
|
|
<h3 class="mb-0">@result.Model</h3>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Duration</p>
|
|
<h3 class="mb-0">@(string.IsNullOrWhiteSpace(result.Duration) ? "Not run" : result.Duration)</h3>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Paragraphs</p>
|
|
<h3 class="mb-0">@result.ParagraphCount.ToString("N0")</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<dl class="mt-3 mb-0">
|
|
<dt>Input tokens</dt>
|
|
<dd>@(result.InputTokens.HasValue ? result.InputTokens.Value.ToString("N0") : "Not reported")</dd>
|
|
<dt>Output tokens</dt>
|
|
<dd>@(result.OutputTokens.HasValue ? result.OutputTokens.Value.ToString("N0") : "Not reported")</dd>
|
|
<dt>Retries</dt>
|
|
<dd>@result.RetryCount.ToString("N0")</dd>
|
|
</dl>
|
|
|
|
<ul class="nav nav-tabs mt-4" id="chapterStructureTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="raw-json-tab" data-bs-toggle="tab" data-bs-target="#raw-json" type="button" role="tab" aria-controls="raw-json" aria-selected="true">Raw JSON</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="validation-tab" data-bs-toggle="tab" data-bs-target="#validation" type="button" role="tab" aria-controls="validation" aria-selected="false">Validation</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="parsed-model-tab" data-bs-toggle="tab" data-bs-target="#parsed-model" type="button" role="tab" aria-controls="parsed-model" aria-selected="false">Parsed Model</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content border border-top-0 rounded-bottom p-3" id="chapterStructureTabContent">
|
|
<div class="tab-pane fade show active" id="raw-json" role="tabpanel" aria-labelledby="raw-json-tab" tabindex="0">
|
|
<div class="mb-3">
|
|
<label class="form-label" for="chapterContextJson">Chapter context JSON</label>
|
|
<textarea class="form-control font-monospace" id="chapterContextJson" rows="8" readonly>@result.ChapterContextJson</textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label" for="chapterJsonText">Extracted Chapter Structure JSON</label>
|
|
<textarea class="form-control font-monospace" id="chapterJsonText" rows="12" readonly>@result.ChapterJsonText</textarea>
|
|
</div>
|
|
<div>
|
|
<label class="form-label" for="rawResponseText">Raw OpenAI response</label>
|
|
<textarea class="form-control font-monospace" id="rawResponseText" rows="18" readonly>@result.RawResponseText</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="validation" role="tabpanel" aria-labelledby="validation-tab" tabindex="0">
|
|
@if (result.Validation is null)
|
|
{
|
|
<div class="alert alert-warning">Validation did not run.</div>
|
|
}
|
|
else
|
|
{
|
|
<h3>@ValidationSummary(result.Validation)</h3>
|
|
@RenderIssues("Errors", result.Validation.Errors)
|
|
@RenderIssues("Warnings", result.Validation.Warnings)
|
|
@RenderIssues("Information", result.Validation.Information)
|
|
}
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="parsed-model" role="tabpanel" aria-labelledby="parsed-model-tab" tabindex="0">
|
|
@if (result.ParsedChapter is null)
|
|
{
|
|
<div class="alert alert-warning">No parsed Chapter Structure model is available.</div>
|
|
}
|
|
else
|
|
{
|
|
<section class="border rounded p-3">
|
|
<h3>Chapter</h3>
|
|
<dl class="mb-0">
|
|
<dt>Schema</dt>
|
|
<dd>@Display(result.ParsedChapter.SchemaVersion)</dd>
|
|
<dt>Summary</dt>
|
|
<dd>@Display(result.ParsedChapter.ChapterSummary)</dd>
|
|
</dl>
|
|
</section>
|
|
|
|
<section class="border rounded p-3 mt-3">
|
|
<h3>Scene Boundaries</h3>
|
|
@if (result.ParsedChapter.SceneBoundaries is null || result.ParsedChapter.SceneBoundaries.Count == 0)
|
|
{
|
|
<p class="mb-0">No scene boundaries reported.</p>
|
|
}
|
|
else
|
|
{
|
|
<ol class="mb-0">
|
|
@foreach (var boundary in result.ParsedChapter.SceneBoundaries)
|
|
{
|
|
<li>
|
|
Scene @Display(boundary.SceneNumber):
|
|
paragraphs @Display(boundary.StartParagraph)-@Display(boundary.EndParagraph),
|
|
confidence @Display(boundary.Confidence).
|
|
@Display(boundary.Reason)
|
|
</li>
|
|
}
|
|
</ol>
|
|
}
|
|
</section>
|
|
}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
}
|
|
|
|
@functions {
|
|
private static string Display(object? value) => value switch
|
|
{
|
|
null => "None",
|
|
string text when string.IsNullOrWhiteSpace(text) => "None",
|
|
decimal number => number.ToString("0.##"),
|
|
bool flag => flag ? "Yes" : "No",
|
|
_ => value.ToString() ?? "None"
|
|
};
|
|
|
|
private static string ValidationSummary(PlotLine.Models.StoryIntelligence.ValidationResult validation)
|
|
=> validation.IsValid
|
|
? $"✓ Passed with {validation.Warnings.Count:N0} warning(s)"
|
|
: $"✖ Failed with {validation.Errors.Count:N0} error(s) and {validation.Warnings.Count:N0} warning(s)";
|
|
|
|
private static string SeveritySymbol(string severity) => severity switch
|
|
{
|
|
"Error" => "✖",
|
|
"Warning" => "⚠",
|
|
_ => "✓"
|
|
};
|
|
|
|
private static Microsoft.AspNetCore.Html.IHtmlContent RenderIssues(string heading, IReadOnlyList<PlotLine.Models.StoryIntelligence.ValidationIssue> issues)
|
|
{
|
|
var builder = new System.Text.StringBuilder();
|
|
builder.Append("<section class=\"mt-3\">");
|
|
builder.Append("<h4>").Append(Encode(heading)).Append(" (").Append(issues.Count.ToString("N0")).Append(")</h4>");
|
|
if (issues.Count == 0)
|
|
{
|
|
builder.Append("<p class=\"mb-0\">None.</p>");
|
|
}
|
|
else
|
|
{
|
|
builder.Append("<div class=\"list-group\">");
|
|
foreach (var issue in issues)
|
|
{
|
|
builder.Append("<div class=\"list-group-item\">");
|
|
builder.Append("<p class=\"mb-1\"><strong>")
|
|
.Append(Encode(SeveritySymbol(issue.Severity)))
|
|
.Append(' ')
|
|
.Append(Encode(issue.Severity))
|
|
.Append("</strong> <code>")
|
|
.Append(Encode(issue.Path))
|
|
.Append("</code></p>");
|
|
builder.Append("<p class=\"mb-1\">").Append(Encode(issue.Message)).Append("</p>");
|
|
builder.Append("<p class=\"mb-0 muted\">").Append(Encode(issue.SuggestedFix)).Append("</p>");
|
|
builder.Append("</div>");
|
|
}
|
|
|
|
builder.Append("</div>");
|
|
}
|
|
|
|
builder.Append("</section>");
|
|
return new Microsoft.AspNetCore.Html.HtmlString(builder.ToString());
|
|
}
|
|
|
|
private static string Encode(string? value)
|
|
=> System.Net.WebUtility.HtmlEncode(value ?? string.Empty);
|
|
}
|