252 lines
11 KiB
Plaintext
252 lines
11 KiB
Plaintext
@model StoryIntelligenceSavedRunDetailViewModel
|
|
@{
|
|
ViewData["Title"] = "Story Intelligence Run";
|
|
var run = Model.Run;
|
|
var canCancel = run is not null && (run.Status == StoryIntelligenceRunStatuses.Pending || run.Status == StoryIntelligenceRunStatuses.Running);
|
|
var shouldRefresh = run is not null && (canCancel || run.Status == "Running");
|
|
}
|
|
|
|
@if (shouldRefresh)
|
|
{
|
|
<meta http-equiv="refresh" content="10" />
|
|
}
|
|
|
|
<div class="page-heading compact">
|
|
<div>
|
|
<p class="eyebrow">Admin utility</p>
|
|
<h1>Story Intelligence Run @(run is null ? string.Empty : run.StoryIntelligenceRunID.ToString("N0"))</h1>
|
|
<p class="lead-text">Saved audit output from the admin chapter-to-scene dry-run pipeline.</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="StoryIntelligenceRuns">Saved Story Intelligence Runs</a>
|
|
<span class="muted"> / Run @(run is null ? string.Empty : run.StoryIntelligenceRunID.ToString("N0"))</span>
|
|
</nav>
|
|
|
|
@if (TempData["AdminMessage"] is string message)
|
|
{
|
|
<div class="alert alert-success">@message</div>
|
|
}
|
|
|
|
@if (run is null)
|
|
{
|
|
<section class="edit-panel">
|
|
<p class="mb-0">This Story Intelligence run could not be found.</p>
|
|
</section>
|
|
}
|
|
else
|
|
{
|
|
<section class="edit-panel">
|
|
<h2>Run metadata</h2>
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Status</p>
|
|
<h3 class="mb-0">@run.Status</h3>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Scenes</p>
|
|
<h3 class="mb-0">@Model.SceneResults.Count.ToString("N0")</h3>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="border rounded p-3 h-100">
|
|
<p class="eyebrow">Tokens</p>
|
|
<h3 class="mb-0">@Display(run.TotalTokens)</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">@DisplayDuration(run.TotalDurationMs)</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<dl class="mt-3 mb-0">
|
|
<dt>Current stage</dt>
|
|
<dd>@Display(run.CurrentStage)</dd>
|
|
<dt>Current message</dt>
|
|
<dd>@Display(run.CurrentMessage)</dd>
|
|
<dt>Scene progress</dt>
|
|
<dd>
|
|
Detected: @Display(run.TotalDetectedScenes),
|
|
completed: @Display(run.CompletedScenes),
|
|
failed: @Display(run.FailedScenes)
|
|
</dd>
|
|
<dt>Source type</dt>
|
|
<dd>@run.SourceType</dd>
|
|
<dt>Source file</dt>
|
|
<dd>@Display(run.SourceFileName)</dd>
|
|
<dt>Source file size</dt>
|
|
<dd>@DisplayBytes(run.SourceFileSizeBytes)</dd>
|
|
<dt>Source word count</dt>
|
|
<dd>@Display(run.SourceWordCount)</dd>
|
|
<dt>Source character count</dt>
|
|
<dd>@Display(run.SourceCharacterCount)</dd>
|
|
<dt>Source chapter count</dt>
|
|
<dd>@Display(run.SourceChapterCount)</dd>
|
|
<dt>Detected document features</dt>
|
|
<dd>
|
|
Images: @Display(run.SourceDetectedImagesCount),
|
|
tables: @Display(run.SourceDetectedTablesCount),
|
|
footnotes: @Display(run.SourceDetectedFootnotesCount),
|
|
comments: @Display(run.SourceDetectedCommentsCount)
|
|
</dd>
|
|
<dt>Project</dt>
|
|
<dd>@DisplayName(run.ProjectTitle, run.ProjectID, "Project")</dd>
|
|
<dt>Book</dt>
|
|
<dd>@DisplayName(run.BookTitle, run.BookID, "Book")</dd>
|
|
<dt>Chapter</dt>
|
|
<dd>@DisplayChapter(run.ChapterID, run.ChapterNumber)</dd>
|
|
<dt>Prompt version</dt>
|
|
<dd>@run.PromptVersion</dd>
|
|
<dt>Prompt versions summary</dt>
|
|
<dd>@Display(run.PromptVersionsSummary)</dd>
|
|
<dt>Model</dt>
|
|
<dd>@run.Model</dd>
|
|
<dt>Started</dt>
|
|
<dd>@run.StartedUtc.ToString("yyyy-MM-dd HH:mm:ss") UTC</dd>
|
|
<dt>Completed</dt>
|
|
<dd>@(run.CompletedUtc?.ToString("yyyy-MM-dd HH:mm:ss") ?? "None") UTC</dd>
|
|
<dt>Cancellation requested</dt>
|
|
<dd>@(run.CancellationRequestedUtc?.ToString("yyyy-MM-dd HH:mm:ss") ?? "None") UTC</dd>
|
|
<dt>Cancelled</dt>
|
|
<dd>@(run.CancelledUtc?.ToString("yyyy-MM-dd HH:mm:ss") ?? "None") UTC</dd>
|
|
<dt>Failure stage</dt>
|
|
<dd>@Display(run.FailureStage)</dd>
|
|
<dt>Input tokens</dt>
|
|
<dd>@Display(run.TotalInputTokens)</dd>
|
|
<dt>Output tokens</dt>
|
|
<dd>@Display(run.TotalOutputTokens)</dd>
|
|
<dt>Estimated cost</dt>
|
|
<dd>@DisplayCost(run.EstimatedCostGBP, "GBP") / @DisplayCost(run.EstimatedCostUSD, "USD")</dd>
|
|
<dt>Error message</dt>
|
|
<dd>@Display(run.ErrorMessage)</dd>
|
|
<dt>Error detail</dt>
|
|
<dd><pre class="mb-0">@Display(run.ErrorDetail)</pre></dd>
|
|
</dl>
|
|
|
|
@if (canCancel)
|
|
{
|
|
<form asp-action="CancelStoryIntelligenceRun" asp-route-id="@run.StoryIntelligenceRunID" method="post" class="mt-3">
|
|
<button class="btn btn-outline-danger" type="submit">Cancel run</button>
|
|
</form>
|
|
}
|
|
</section>
|
|
|
|
<section class="edit-panel">
|
|
<h2>Chapter result</h2>
|
|
@if (Model.ChapterResult is null)
|
|
{
|
|
<p class="mb-0">No chapter result was saved for this run.</p>
|
|
}
|
|
else
|
|
{
|
|
var chapter = Model.ChapterResult;
|
|
<dl>
|
|
<dt>Source label</dt>
|
|
<dd>@chapter.SourceLabel</dd>
|
|
<dt>Chapter</dt>
|
|
<dd>@Display(chapter.ChapterNumber)</dd>
|
|
<dt>Prompt version</dt>
|
|
<dd>@chapter.PromptVersion</dd>
|
|
<dt>Validation</dt>
|
|
<dd>@chapter.ValidationErrorsCount.ToString("N0") error(s), @chapter.ValidationWarningsCount.ToString("N0") warning(s)</dd>
|
|
<dt>Duration</dt>
|
|
<dd>@DisplayDuration(chapter.DurationMs)</dd>
|
|
<dt>Tokens</dt>
|
|
<dd>@Display(chapter.TotalTokens) total (@Display(chapter.InputTokens) input, @Display(chapter.OutputTokens) output)</dd>
|
|
</dl>
|
|
|
|
<label class="form-label" for="chapterParsedJson">Parsed JSON</label>
|
|
<textarea class="form-control font-monospace" id="chapterParsedJson" rows="10" readonly>@chapter.ParsedJson</textarea>
|
|
<label class="form-label mt-3" for="chapterOutputJson">Extracted output JSON</label>
|
|
<textarea class="form-control font-monospace" id="chapterOutputJson" rows="10" readonly>@chapter.OutputTextJson</textarea>
|
|
<label class="form-label mt-3" for="chapterRawJson">Raw OpenAI response JSON</label>
|
|
<textarea class="form-control font-monospace" id="chapterRawJson" rows="10" readonly>@chapter.RawResponseJson</textarea>
|
|
}
|
|
</section>
|
|
|
|
<section class="edit-panel">
|
|
<h2>Scene results</h2>
|
|
@if (Model.SceneResults.Count == 0)
|
|
{
|
|
<p class="mb-0">No scene results were saved for this run.</p>
|
|
}
|
|
</section>
|
|
|
|
@foreach (var scene in Model.SceneResults)
|
|
{
|
|
<section class="edit-panel">
|
|
<h2>Suggested Scene @scene.TemporarySceneNumber.ToString("N0")</h2>
|
|
<dl>
|
|
<dt>Paragraph range</dt>
|
|
<dd>@Display(scene.StartParagraph)-@Display(scene.EndParagraph)</dd>
|
|
<dt>Source label</dt>
|
|
<dd>@scene.SourceLabel</dd>
|
|
<dt>Prompt version</dt>
|
|
<dd>@scene.PromptVersion</dd>
|
|
<dt>Validation</dt>
|
|
<dd>@scene.ValidationErrorsCount.ToString("N0") error(s), @scene.ValidationWarningsCount.ToString("N0") warning(s)</dd>
|
|
<dt>Duration</dt>
|
|
<dd>@DisplayDuration(scene.DurationMs)</dd>
|
|
<dt>Tokens</dt>
|
|
<dd>@Display(scene.TotalTokens) total (@Display(scene.InputTokens) input, @Display(scene.OutputTokens) output)</dd>
|
|
</dl>
|
|
|
|
<label class="form-label" for="sceneParsedJson-@scene.SceneResultID">Parsed JSON</label>
|
|
<textarea class="form-control font-monospace" id="sceneParsedJson-@scene.SceneResultID" rows="10" readonly>@scene.ParsedJson</textarea>
|
|
<label class="form-label mt-3" for="sceneOutputJson-@scene.SceneResultID">Extracted output JSON</label>
|
|
<textarea class="form-control font-monospace" id="sceneOutputJson-@scene.SceneResultID" rows="10" readonly>@scene.OutputTextJson</textarea>
|
|
<label class="form-label mt-3" for="sceneRawJson-@scene.SceneResultID">Raw OpenAI response JSON</label>
|
|
<textarea class="form-control font-monospace" id="sceneRawJson-@scene.SceneResultID" rows="10" readonly>@scene.RawResponseJson</textarea>
|
|
</section>
|
|
}
|
|
}
|
|
|
|
@functions {
|
|
private static string Display(object? value) => value switch
|
|
{
|
|
null => "None",
|
|
string text when string.IsNullOrWhiteSpace(text) => "None",
|
|
decimal number => number.ToString("0.##"),
|
|
int number => number.ToString("N0"),
|
|
_ => value.ToString() ?? "None"
|
|
};
|
|
|
|
private static string DisplayDuration(long? milliseconds)
|
|
=> milliseconds.HasValue
|
|
? TimeSpan.FromMilliseconds(milliseconds.Value).TotalSeconds < 1
|
|
? $"{milliseconds.Value:N0} ms"
|
|
: $"{TimeSpan.FromMilliseconds(milliseconds.Value).TotalSeconds:0.00} sec"
|
|
: "None";
|
|
|
|
private static string DisplayBytes(long? bytes)
|
|
=> bytes.HasValue ? $"{bytes.Value:N0} bytes" : "None";
|
|
|
|
private static string DisplayCost(decimal? value, string currency)
|
|
=> value.HasValue ? $"{value.Value:0.000000} {currency}" : "None";
|
|
|
|
private static string DisplayName(string? title, int? id, string label)
|
|
=> !string.IsNullOrWhiteSpace(title)
|
|
? title
|
|
: id.HasValue
|
|
? $"{label} {id.Value:N0}"
|
|
: "None";
|
|
|
|
private static string DisplayChapter(int? chapterId, decimal? chapterNumber)
|
|
=> chapterId.HasValue
|
|
? chapterNumber.HasValue
|
|
? $"Chapter {chapterNumber.Value:0.##} (ID {chapterId.Value:N0})"
|
|
: $"Chapter ID {chapterId.Value:N0}"
|
|
: "None";
|
|
}
|