Phase 20AG – Fix Progress Card Wrapping and Add Chapter Scene Progress
This commit is contained in:
parent
766ad28325
commit
2097122898
@ -1,4 +1,5 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using PlotLine.Data;
|
using PlotLine.Data;
|
||||||
@ -37,9 +38,13 @@ public sealed class StoryIntelligenceHub(
|
|||||||
await Groups.AddToGroupAsync(Context.ConnectionId, UserGroup(userId));
|
await Groups.AddToGroupAsync(Context.ConnectionId, UserGroup(userId));
|
||||||
|
|
||||||
var run = await persistedRuns.GetRunAsync(runId);
|
var run = await persistedRuns.GetRunAsync(runId);
|
||||||
return run is null || run.UserID != userId
|
if (run is null || run.UserID != userId)
|
||||||
? null
|
{
|
||||||
: ToRunProgress(run, "Current status");
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sceneResults = await persistedRuns.ListSceneResultsAsync(runId);
|
||||||
|
return ToRunProgress(run, "Current status", LatestSceneSummary(sceneResults));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<StoryIntelligenceProgressViewModel?> WatchOnboardingStoryIntelligenceBatch(Guid batchId)
|
public async Task<StoryIntelligenceProgressViewModel?> WatchOnboardingStoryIntelligenceBatch(Guid batchId)
|
||||||
@ -51,7 +56,7 @@ public sealed class StoryIntelligenceHub(
|
|||||||
|
|
||||||
public static string UserGroup(int userId) => $"story-intelligence:{userId}";
|
public static string UserGroup(int userId) => $"story-intelligence:{userId}";
|
||||||
|
|
||||||
private static StoryIntelligenceRunProgressEvent ToRunProgress(StoryIntelligenceSavedRun run, string eventType)
|
private static StoryIntelligenceRunProgressEvent ToRunProgress(StoryIntelligenceSavedRun run, string eventType, string? latestSceneSummary)
|
||||||
{
|
{
|
||||||
var completedScenes = run.CompletedScenes ?? 0;
|
var completedScenes = run.CompletedScenes ?? 0;
|
||||||
var totalScenes = run.TotalDetectedScenes ?? 0;
|
var totalScenes = run.TotalDetectedScenes ?? 0;
|
||||||
@ -77,6 +82,7 @@ public sealed class StoryIntelligenceHub(
|
|||||||
CurrentSceneNumber = run.CompletedScenes,
|
CurrentSceneNumber = run.CompletedScenes,
|
||||||
ElapsedTime = FormatDuration(elapsedMs),
|
ElapsedTime = FormatDuration(elapsedMs),
|
||||||
EstimatedRemaining = EstimateRemaining(elapsedMs, completedScenes, totalScenes),
|
EstimatedRemaining = EstimateRemaining(elapsedMs, completedScenes, totalScenes),
|
||||||
|
LatestSceneSummary = latestSceneSummary,
|
||||||
ErrorMessage = run.ErrorMessage,
|
ErrorMessage = run.ErrorMessage,
|
||||||
UpdatedUtc = run.UpdatedUtc,
|
UpdatedUtc = run.UpdatedUtc,
|
||||||
IsActive = run.Status is StoryIntelligenceRunStatuses.Pending or StoryIntelligenceRunStatuses.Running,
|
IsActive = run.Status is StoryIntelligenceRunStatuses.Pending or StoryIntelligenceRunStatuses.Running,
|
||||||
@ -106,6 +112,85 @@ public sealed class StoryIntelligenceHub(
|
|||||||
return FormatDuration(averageSceneMs * (totalScenes - completedScenes));
|
return FormatDuration(averageSceneMs * (totalScenes - completedScenes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? LatestSceneSummary(IReadOnlyList<StoryIntelligenceSavedSceneResult> sceneResults)
|
||||||
|
{
|
||||||
|
foreach (var scene in sceneResults
|
||||||
|
.Where(scene => scene.ValidationErrorsCount == 0 && !string.IsNullOrWhiteSpace(scene.ParsedJson))
|
||||||
|
.OrderByDescending(scene => scene.TemporarySceneNumber))
|
||||||
|
{
|
||||||
|
var summary = ExtractShortSummary(scene.ParsedJson);
|
||||||
|
if (!string.IsNullOrWhiteSpace(summary))
|
||||||
|
{
|
||||||
|
return Trim(summary, 220);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractShortSummary(string json)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var document = JsonDocument.Parse(json);
|
||||||
|
var root = document.RootElement;
|
||||||
|
if (TryReadShortSummary(root, out var direct))
|
||||||
|
{
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("parsedScene", out var parsedScene)
|
||||||
|
&& TryReadShortSummary(parsedScene, out var wrapped))
|
||||||
|
{
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadShortSummary(JsonElement element, out string? summary)
|
||||||
|
{
|
||||||
|
summary = null;
|
||||||
|
if (element.ValueKind != JsonValueKind.Object
|
||||||
|
|| !element.TryGetProperty("summary", out var summaryElement)
|
||||||
|
|| summaryElement.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryReadSummaryValue(summaryElement, "short", out summary))
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryReadSummaryValue(summaryElement, "detailed", out summary)
|
||||||
|
&& !string.IsNullOrWhiteSpace(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadSummaryValue(JsonElement summaryElement, string propertyName, out string? value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
if (summaryElement.TryGetProperty(propertyName, out var textElement)
|
||||||
|
&& textElement.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
value = textElement.GetString();
|
||||||
|
return !string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Trim(string value, int maxLength)
|
||||||
|
{
|
||||||
|
var trimmed = value.Trim();
|
||||||
|
return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength].TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
private int RequireUserId()
|
private int RequireUserId()
|
||||||
=> TryGetUserId(out var userId) ? userId : throw new HubException("Sign in to use Story Intelligence.");
|
=> TryGetUserId(out var userId) ? userId : throw new HubException("Sign in to use Story Intelligence.");
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text.Json;
|
||||||
using PlotLine.Data;
|
using PlotLine.Data;
|
||||||
using PlotLine.Models;
|
using PlotLine.Models;
|
||||||
using PlotLine.ViewModels;
|
using PlotLine.ViewModels;
|
||||||
@ -216,6 +217,7 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
var confirmation = run.Status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings
|
var confirmation = run.Status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings
|
||||||
? await commits.BuildConfirmationAsync(item.RunID)
|
? await commits.BuildConfirmationAsync(item.RunID)
|
||||||
: null;
|
: null;
|
||||||
|
var sceneResults = await runs.ListSceneResultsAsync(item.RunID);
|
||||||
|
|
||||||
chapters.Add(new StoryIntelligenceOnboardingChapterViewModel
|
chapters.Add(new StoryIntelligenceOnboardingChapterViewModel
|
||||||
{
|
{
|
||||||
@ -235,6 +237,7 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
TotalDurationMs = run.TotalDurationMs,
|
TotalDurationMs = run.TotalDurationMs,
|
||||||
FailureStage = run.FailureStage,
|
FailureStage = run.FailureStage,
|
||||||
ErrorMessage = run.ErrorMessage,
|
ErrorMessage = run.ErrorMessage,
|
||||||
|
LatestSceneSummary = LatestSceneSummary(sceneResults),
|
||||||
HasCompletedCommit = commit is not null && string.Equals(commit.Status, StoryIntelligenceImportCommitStatuses.Completed, StringComparison.OrdinalIgnoreCase),
|
HasCompletedCommit = commit is not null && string.Equals(commit.Status, StoryIntelligenceImportCommitStatuses.Completed, StringComparison.OrdinalIgnoreCase),
|
||||||
CanCommit = confirmation?.CanCommit == true,
|
CanCommit = confirmation?.CanCommit == true,
|
||||||
ScenesCreated = commit?.ScenesCreated ?? 0,
|
ScenesCreated = commit?.ScenesCreated ?? 0,
|
||||||
@ -347,6 +350,85 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
? 0
|
? 0
|
||||||
: value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
|
: value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
|
||||||
|
|
||||||
|
private static string? LatestSceneSummary(IReadOnlyList<StoryIntelligenceSavedSceneResult> sceneResults)
|
||||||
|
{
|
||||||
|
foreach (var scene in sceneResults
|
||||||
|
.Where(scene => scene.ValidationErrorsCount == 0 && !string.IsNullOrWhiteSpace(scene.ParsedJson))
|
||||||
|
.OrderByDescending(scene => scene.TemporarySceneNumber))
|
||||||
|
{
|
||||||
|
var summary = ExtractShortSummary(scene.ParsedJson);
|
||||||
|
if (!string.IsNullOrWhiteSpace(summary))
|
||||||
|
{
|
||||||
|
return Trim(summary, 220);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ExtractShortSummary(string json)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var document = JsonDocument.Parse(json);
|
||||||
|
var root = document.RootElement;
|
||||||
|
if (TryReadShortSummary(root, out var direct))
|
||||||
|
{
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("parsedScene", out var parsedScene)
|
||||||
|
&& TryReadShortSummary(parsedScene, out var wrapped))
|
||||||
|
{
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadShortSummary(JsonElement element, out string? summary)
|
||||||
|
{
|
||||||
|
summary = null;
|
||||||
|
if (element.ValueKind != JsonValueKind.Object
|
||||||
|
|| !element.TryGetProperty("summary", out var summaryElement)
|
||||||
|
|| summaryElement.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryReadSummaryValue(summaryElement, "short", out summary))
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryReadSummaryValue(summaryElement, "detailed", out summary)
|
||||||
|
&& !string.IsNullOrWhiteSpace(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadSummaryValue(JsonElement summaryElement, string propertyName, out string? value)
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
if (summaryElement.TryGetProperty(propertyName, out var textElement)
|
||||||
|
&& textElement.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
value = textElement.GetString();
|
||||||
|
return !string.IsNullOrWhiteSpace(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Trim(string value, int maxLength)
|
||||||
|
{
|
||||||
|
var trimmed = value.Trim();
|
||||||
|
return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength].TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record IncludedChapter(ManuscriptScanChapterReviewDecision Decision, ManuscriptScanChapterPreview Preview);
|
private sealed record IncludedChapter(ManuscriptScanChapterReviewDecision Decision, ManuscriptScanChapterPreview Preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -343,7 +343,7 @@ public sealed class PersistedStoryIntelligenceRunner(
|
|||||||
failedScenes: failedScenes,
|
failedScenes: failedScenes,
|
||||||
totalTokens: totals.TotalTokens,
|
totalTokens: totals.TotalTokens,
|
||||||
currentSceneNumber: block.TemporarySceneNumber,
|
currentSceneNumber: block.TemporarySceneNumber,
|
||||||
latestSceneSummary: TrimOptional(sceneAttempt.Parsed?.Summary?.Short, 220),
|
latestSceneSummary: SceneSummaryText(sceneAttempt.Parsed),
|
||||||
recentDiscoveries: BuildDiscoveries(sceneAttempt.Parsed));
|
recentDiscoveries: BuildDiscoveries(sceneAttempt.Parsed));
|
||||||
if (!validation.IsValid || validation.Warnings.Count > 0)
|
if (!validation.IsValid || validation.Warnings.Count > 0)
|
||||||
{
|
{
|
||||||
@ -608,6 +608,11 @@ public sealed class PersistedStoryIntelligenceRunner(
|
|||||||
: $"Relationship signal found: {characterA} and {characterB} - {signal}";
|
: $"Relationship signal found: {characterA} and {characterB} - {signal}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? SceneSummaryText(SceneIntelligenceScene? scene)
|
||||||
|
=> TrimOptional(
|
||||||
|
FirstConfigured(scene?.Summary?.Short, scene?.Summary?.Detailed),
|
||||||
|
220);
|
||||||
|
|
||||||
private static string? TrimOptional(string? value, int maxLength)
|
private static string? TrimOptional(string? value, int maxLength)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
@ -619,6 +624,9 @@ public sealed class PersistedStoryIntelligenceRunner(
|
|||||||
return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength].TrimEnd();
|
return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength].TrimEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? FirstConfigured(params string?[] values)
|
||||||
|
=> values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value));
|
||||||
|
|
||||||
private async Task<AiJsonParseResult<T>> ParseAiJsonWithRetryAsync<T>(
|
private async Task<AiJsonParseResult<T>> ParseAiJsonWithRetryAsync<T>(
|
||||||
string completedPrompt,
|
string completedPrompt,
|
||||||
string promptVersion,
|
string promptVersion,
|
||||||
|
|||||||
@ -171,6 +171,7 @@ public sealed class StoryIntelligenceOnboardingChapterViewModel
|
|||||||
public long? TotalDurationMs { get; init; }
|
public long? TotalDurationMs { get; init; }
|
||||||
public string? FailureStage { get; init; }
|
public string? FailureStage { get; init; }
|
||||||
public string? ErrorMessage { get; init; }
|
public string? ErrorMessage { get; init; }
|
||||||
|
public string? LatestSceneSummary { get; init; }
|
||||||
public bool HasCompletedCommit { get; init; }
|
public bool HasCompletedCommit { get; init; }
|
||||||
public bool CanCommit { get; init; }
|
public bool CanCommit { get; init; }
|
||||||
public int ScenesCreated { get; init; }
|
public int ScenesCreated { get; init; }
|
||||||
|
|||||||
@ -46,11 +46,11 @@
|
|||||||
<div class="story-live-stats">
|
<div class="story-live-stats">
|
||||||
<div>
|
<div>
|
||||||
<span>Chapters read</span>
|
<span>Chapters read</span>
|
||||||
<strong class="story-stat-value"><span data-story-chapters-completed>@Model.CompletedChapterCount</span> of @Model.ChapterCount</strong>
|
<strong class="story-stat-value" data-story-chapters-read-value>@Model.CompletedChapterCount of @Model.ChapterCount</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Scenes read</span>
|
<span>Scenes read</span>
|
||||||
<strong class="story-stat-value"><span data-story-scenes-completed>@Model.TotalCompletedScenes</span> so far</strong>
|
<strong class="story-stat-value" data-story-scenes-read-value>@Model.TotalCompletedScenes so far</strong>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Needs review</span>
|
<span>Needs review</span>
|
||||||
@ -73,17 +73,31 @@
|
|||||||
<h2 data-story-current-chapter>@(currentChapter?.ChapterTitle ?? "Waiting")</h2>
|
<h2 data-story-current-chapter>@(currentChapter?.ChapterTitle ?? "Waiting")</h2>
|
||||||
<p data-story-current-message>@FriendlyMessage(currentChapter)</p>
|
<p data-story-current-message>@FriendlyMessage(currentChapter)</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="story-current-scene-panel">
|
||||||
<p class="eyebrow">Current scene</p>
|
<p class="eyebrow">Current scene</p>
|
||||||
<strong data-story-current-scene>@CurrentScene(Model)</strong>
|
<strong data-story-current-scene>@CurrentScene(Model)</strong>
|
||||||
|
<div class="story-chapter-progress">
|
||||||
|
<span data-story-chapter-progress-text>@ChapterProgressText(currentChapter)</span>
|
||||||
|
<div class="progress" role="progressbar" aria-label="Current chapter progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="@ChapterProgressPercent(currentChapter)">
|
||||||
|
<div class="progress-bar" data-story-chapter-progress-bar style="width:@ChapterProgressPercent(currentChapter)%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="story-live-feed-grid" aria-label="Live manuscript feed">
|
<section class="story-live-feed-grid" aria-label="Live manuscript feed">
|
||||||
<div>
|
<div>
|
||||||
<h2>Latest from your manuscript</h2>
|
<h2>Latest from your manuscript</h2>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(currentChapter?.LatestSceneSummary))
|
||||||
|
{
|
||||||
|
<p class="lead story-latest-summary" data-story-latest-summary>@currentChapter.LatestSceneSummary</p>
|
||||||
|
<p class="text-muted" data-story-latest-empty hidden>Scene summaries will appear here as PlotDirector reads.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<p class="lead story-latest-summary" data-story-latest-summary hidden></p>
|
<p class="lead story-latest-summary" data-story-latest-summary hidden></p>
|
||||||
<p class="text-muted" data-story-latest-empty>Scene summaries will appear here as PlotDirector reads.</p>
|
<p class="text-muted" data-story-latest-empty>Scene summaries will appear here as PlotDirector reads.</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="story-live-discoveries">
|
<div class="story-live-discoveries">
|
||||||
<h2>Recent discoveries</h2>
|
<h2>Recent discoveries</h2>
|
||||||
@ -106,6 +120,7 @@
|
|||||||
<span data-story-run-failed>@((chapter.FailedScenes ?? 0).ToString("N0"))</span>
|
<span data-story-run-failed>@((chapter.FailedScenes ?? 0).ToString("N0"))</span>
|
||||||
<span data-story-run-message>@FriendlyMessage(chapter)</span>
|
<span data-story-run-message>@FriendlyMessage(chapter)</span>
|
||||||
<span data-story-run-error>@(chapter.ErrorMessage ?? string.Empty)</span>
|
<span data-story-run-error>@(chapter.ErrorMessage ?? string.Empty)</span>
|
||||||
|
<span data-story-run-latest-summary>@(chapter.LatestSceneSummary ?? string.Empty)</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -314,4 +329,27 @@
|
|||||||
? chapter.CompletedScenes.Value.ToString("N0")
|
? chapter.CompletedScenes.Value.ToString("N0")
|
||||||
: "Waiting";
|
: "Waiting";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ChapterProgressText(StoryIntelligenceOnboardingChapterViewModel? chapter)
|
||||||
|
{
|
||||||
|
if ((chapter?.TotalDetectedScenes ?? 0) <= 0)
|
||||||
|
{
|
||||||
|
return "Finding scenes...";
|
||||||
|
}
|
||||||
|
|
||||||
|
var completed = Math.Clamp(chapter?.CompletedScenes ?? 0, 0, chapter?.TotalDetectedScenes ?? 0);
|
||||||
|
return $"Scene {completed:N0} of {chapter?.TotalDetectedScenes:N0}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ChapterProgressPercent(StoryIntelligenceOnboardingChapterViewModel? chapter)
|
||||||
|
{
|
||||||
|
var total = chapter?.TotalDetectedScenes ?? 0;
|
||||||
|
if (total <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var completed = Math.Clamp(chapter?.CompletedScenes ?? 0, 0, total);
|
||||||
|
return Math.Clamp((int)Math.Floor(completed * 100m / total), 0, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -711,9 +711,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.story-stat-value {
|
.story-stat-value {
|
||||||
display: inline-flex;
|
display: inline-block;
|
||||||
align-items: baseline;
|
|
||||||
gap: .25rem;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@ -734,6 +732,29 @@
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.story-current-scene-panel {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: .35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-chapter-progress {
|
||||||
|
display: grid;
|
||||||
|
gap: .35rem;
|
||||||
|
margin-top: .2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-chapter-progress span {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
font-size: .9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-chapter-progress .progress {
|
||||||
|
height: .45rem;
|
||||||
|
}
|
||||||
|
|
||||||
.story-live-feed-grid {
|
.story-live-feed-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
|
|||||||
@ -285,11 +285,11 @@
|
|||||||
root.querySelectorAll("[data-story-overall-progress-bar]").forEach((node) => {
|
root.querySelectorAll("[data-story-overall-progress-bar]").forEach((node) => {
|
||||||
node.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
node.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
||||||
});
|
});
|
||||||
root.querySelectorAll("[data-story-chapters-completed]").forEach((node) => {
|
root.querySelectorAll("[data-story-chapters-read-value]").forEach((node) => {
|
||||||
node.textContent = numberText(totals.completedChapters);
|
node.textContent = `${numberText(totals.completedChapters)} of ${numberText(chapters.length)}`;
|
||||||
});
|
});
|
||||||
root.querySelectorAll("[data-story-scenes-completed]").forEach((node) => {
|
root.querySelectorAll("[data-story-scenes-read-value]").forEach((node) => {
|
||||||
node.textContent = numberText(totals.completedScenes);
|
node.textContent = `${numberText(totals.completedScenes)} so far`;
|
||||||
});
|
});
|
||||||
root.querySelectorAll("[data-story-scenes-total]").forEach((node) => {
|
root.querySelectorAll("[data-story-scenes-total]").forEach((node) => {
|
||||||
node.textContent = numberText(totals.totalScenes);
|
node.textContent = numberText(totals.totalScenes);
|
||||||
@ -386,6 +386,11 @@
|
|||||||
node.textContent = error || "";
|
node.textContent = error || "";
|
||||||
node.hidden = !error;
|
node.hidden = !error;
|
||||||
});
|
});
|
||||||
|
chapter.querySelectorAll("[data-story-run-latest-summary]").forEach((node) => {
|
||||||
|
if (latestSummary) {
|
||||||
|
node.textContent = latestSummary;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
root.querySelectorAll("[data-story-current-chapter]").forEach((node) => {
|
root.querySelectorAll("[data-story-current-chapter]").forEach((node) => {
|
||||||
node.textContent = chapter.dataset.storyChapterTitle || "Current chapter";
|
node.textContent = chapter.dataset.storyChapterTitle || "Current chapter";
|
||||||
@ -396,6 +401,18 @@
|
|||||||
root.querySelectorAll("[data-story-current-scene]").forEach((node) => {
|
root.querySelectorAll("[data-story-current-scene]").forEach((node) => {
|
||||||
node.textContent = currentSceneNumber ? numberText(currentSceneNumber) : "Waiting";
|
node.textContent = currentSceneNumber ? numberText(currentSceneNumber) : "Waiting";
|
||||||
});
|
});
|
||||||
|
const totalNumber = Number.parseInt(total || "0", 10) || 0;
|
||||||
|
const completedNumber = Number.parseInt(completed || "0", 10) || 0;
|
||||||
|
const currentSceneDisplay = currentSceneNumber ? Number.parseInt(currentSceneNumber, 10) || completedNumber : completedNumber;
|
||||||
|
root.querySelectorAll("[data-story-chapter-progress-text]").forEach((node) => {
|
||||||
|
node.textContent = totalNumber > 0
|
||||||
|
? `Scene ${numberText(Math.min(Math.max(currentSceneDisplay, 0), totalNumber))} of ${numberText(totalNumber)}`
|
||||||
|
: "Finding scenes...";
|
||||||
|
});
|
||||||
|
root.querySelectorAll("[data-story-chapter-progress-bar]").forEach((node) => {
|
||||||
|
const percent = totalNumber > 0 ? Math.floor((Math.min(completedNumber, totalNumber) * 100) / totalNumber) : 0;
|
||||||
|
node.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
||||||
|
});
|
||||||
root.querySelectorAll("[data-story-current-message]").forEach((node) => {
|
root.querySelectorAll("[data-story-current-message]").forEach((node) => {
|
||||||
node.textContent = displayMessage;
|
node.textContent = displayMessage;
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user