Phase 21J - Story Memory Visualisation Refinement
This commit is contained in:
parent
b78a9bea9b
commit
4ebbfdab92
@ -115,7 +115,8 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
.ToList();
|
||||
|
||||
var current = PickCurrentScene(run, completed);
|
||||
var scenes = BuildSceneStates(run, parsed, completed, current).ToList();
|
||||
var storyMemory = new StoryMemory();
|
||||
var scenes = BuildSceneStates(run, parsed, completed, current, storyMemory: storyMemory).ToList();
|
||||
if (scenes.Count == 0)
|
||||
{
|
||||
scenes.Add(BuildEmptyScene(run));
|
||||
@ -130,7 +131,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
ChangeToken = ChangeToken(run, results),
|
||||
IsTerminal = IsTerminal(run.Status),
|
||||
SnapshotMessage = SnapshotMessage(run, completed.Count),
|
||||
Diagnostics = BuildDiagnostics(run, results, parsed, completed, current),
|
||||
Diagnostics = BuildDiagnostics(run, results, parsed, completed, current, storyMemory),
|
||||
ProjectName = DisplayTitle(run.ProjectTitle, "Untitled project"),
|
||||
BookTitle = DisplayTitle(run.BookTitle, "Untitled book"),
|
||||
Scenes = scenes
|
||||
@ -141,6 +142,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
if (run.ProjectID.HasValue)
|
||||
{
|
||||
await illustrationMatcher.ApplyCharacterIllustrationsAsync(run.ProjectID.Value, model);
|
||||
ApplyIllustrationDiagnostics(model);
|
||||
}
|
||||
logger.LogInformation("Built Story Intelligence visualisation snapshot for run {RunId} in {ElapsedMs}ms.", runId, stopwatch.ElapsedMilliseconds);
|
||||
return model;
|
||||
@ -198,6 +200,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
var overallStage = isSuccessfullyComplete ? "Analysis Complete" : isTerminal ? "Analysis stopped" : BackendStageDisplay(activeSnapshot.Run);
|
||||
var scenes = new List<StoryIntelligenceExperienceSceneState>();
|
||||
var nextSceneNumber = 1;
|
||||
var storyMemory = new StoryMemory();
|
||||
foreach (var snapshot in runSnapshots)
|
||||
{
|
||||
var runScenes = BuildSceneStates(
|
||||
@ -215,7 +218,8 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
overallElapsed: overallElapsed,
|
||||
overallRemaining: overallRemaining,
|
||||
continuousSceneNumbering: true,
|
||||
overallTerminal: isTerminal)
|
||||
overallTerminal: isTerminal,
|
||||
storyMemory: storyMemory)
|
||||
.ToList();
|
||||
scenes.AddRange(runScenes);
|
||||
nextSceneNumber += snapshot.Completed.Count;
|
||||
@ -236,7 +240,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
ChangeToken = SessionChangeToken(session, runSnapshots),
|
||||
IsTerminal = isTerminal,
|
||||
SnapshotMessage = SessionSnapshotMessage(session, runSnapshots),
|
||||
Diagnostics = BuildSessionDiagnostics(session, runSnapshots, activeSnapshot, current, parsedScenes, totalPersistedResults, totalScenes, overallProgressPercent),
|
||||
Diagnostics = BuildSessionDiagnostics(session, runSnapshots, activeSnapshot, current, parsedScenes, totalPersistedResults, totalScenes, overallProgressPercent, storyMemory),
|
||||
ProjectName = DisplayTitle(session.ProjectName, "Untitled project"),
|
||||
BookTitle = DisplayTitle(session.BookDisplayTitle, "Untitled book"),
|
||||
Scenes = scenes
|
||||
@ -245,6 +249,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
StabiliseCharacterIllustrations(model);
|
||||
await illustrationLibrary.ApplyApprovedIllustrationsAsync(model);
|
||||
await illustrationMatcher.ApplyCharacterIllustrationsAsync(session.ProjectID, model);
|
||||
ApplyIllustrationDiagnostics(model);
|
||||
logger.LogInformation("Built Story Intelligence import session visualisation snapshot for session {ImportSessionId} in {ElapsedMs}ms.", importSessionId, stopwatch.ElapsedMilliseconds);
|
||||
return model;
|
||||
}
|
||||
@ -264,14 +269,17 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
string? overallElapsed = null,
|
||||
string? overallRemaining = null,
|
||||
bool continuousSceneNumbering = false,
|
||||
bool overallTerminal = false)
|
||||
bool overallTerminal = false,
|
||||
StoryMemory? storyMemory = null)
|
||||
{
|
||||
storyMemory ??= new StoryMemory();
|
||||
var totalScenes = Math.Max(run.TotalDetectedScenes ?? 0, allResults.Count);
|
||||
var completedScenes = run.CompletedScenes ?? completed.Count;
|
||||
var displayIndex = 0;
|
||||
foreach (var item in completed)
|
||||
{
|
||||
var scene = item.Scene!;
|
||||
storyMemory.Update(scene);
|
||||
var sourceSceneNumber = SceneNumber(item);
|
||||
var sceneNumber = continuousSceneNumbering ? continuousSceneNumberStart + displayIndex++ : sourceSceneNumber;
|
||||
var isCurrent = current?.Result.SceneResultID == item.Result.SceneResultID;
|
||||
@ -295,11 +303,11 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
SceneTotal = Math.Max(1, overallTotalScenes ?? totalScenes),
|
||||
ElapsedTime = overallElapsed ?? EstimateElapsed(run),
|
||||
EstimatedRemaining = overallRemaining ?? EstimateRemaining(run, completedScenes, totalScenes),
|
||||
PovCharacterId = CharacterId(ResolveNarratorName(scene) ?? scene.PointOfView?.CharacterName),
|
||||
PovCharacterId = CharacterId(storyMemory.ResolvePointOfView(scene)),
|
||||
Location = BuildLocation(scene),
|
||||
Characters = BuildCharacters(scene),
|
||||
Characters = BuildCharacters(scene, storyMemory),
|
||||
Assets = BuildAssets(scene),
|
||||
Relationships = BuildRelationships(scene),
|
||||
Relationships = BuildRelationships(scene, storyMemory),
|
||||
KnowledgeThreads = BuildKnowledge(scene),
|
||||
Observations = BuildObservations(scene),
|
||||
LatestDiscoveries = isCurrent ? BuildDiscoveries(scene).ToList() : [],
|
||||
@ -341,12 +349,13 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
return completed.Last();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<StoryIntelligenceExperienceCharacterState> BuildCharacters(SceneIntelligenceScene scene)
|
||||
private static IReadOnlyList<StoryIntelligenceExperienceCharacterState> BuildCharacters(SceneIntelligenceScene scene, StoryMemory? storyMemory = null)
|
||||
{
|
||||
var narratorResolution = ResolveNarratorName(scene);
|
||||
var narratorResolution = storyMemory?.ResolvePointOfView(scene) ?? ResolveNarratorName(scene);
|
||||
var povName = Clean(narratorResolution ?? scene.PointOfView?.CharacterName);
|
||||
var states = new List<StoryIntelligenceExperienceCharacterState>();
|
||||
var reservedCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var sceneCharacterNames = new HashSet<string>((scene.Characters ?? []).Select(character => Clean(character.Name)), StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var group in (scene.Characters ?? [])
|
||||
.Where(character => !string.IsNullOrWhiteSpace(character.Name))
|
||||
.GroupBy(character => Clean(character.Name), StringComparer.OrdinalIgnoreCase)
|
||||
@ -388,6 +397,28 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
});
|
||||
}
|
||||
|
||||
if (storyMemory is not null)
|
||||
{
|
||||
foreach (var memoryCharacter in storyMemory.Characters
|
||||
.Where(character => !sceneCharacterNames.Contains(character.Name))
|
||||
.OrderByDescending(character => character.Weight)
|
||||
.Take(Math.Max(0, 5 - states.Count)))
|
||||
{
|
||||
var libraryCode = CharacterLibraryCode(memoryCharacter.Name, null, scene, reservedCodes);
|
||||
reservedCodes.Add(libraryCode);
|
||||
states.Add(new StoryIntelligenceExperienceCharacterState
|
||||
{
|
||||
Id = CharacterId(memoryCharacter.Name),
|
||||
LibraryCode = libraryCode,
|
||||
Name = memoryCharacter.Name,
|
||||
Role = memoryCharacter.Role,
|
||||
Relevance = memoryCharacter.Relevance,
|
||||
Weight = Math.Max(38, memoryCharacter.Weight - 18),
|
||||
ImagePath = CharacterFallback(memoryCharacter.Name)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return states.OrderByDescending(character => character.Weight).ToList();
|
||||
}
|
||||
|
||||
@ -493,8 +524,9 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
.Take(5)
|
||||
.ToList();
|
||||
|
||||
private static IReadOnlyList<StoryIntelligenceExperienceRelationshipState> BuildRelationships(SceneIntelligenceScene scene)
|
||||
=> (scene.Relationships ?? [])
|
||||
private static IReadOnlyList<StoryIntelligenceExperienceRelationshipState> BuildRelationships(SceneIntelligenceScene scene, StoryMemory? storyMemory = null)
|
||||
{
|
||||
var current = (scene.Relationships ?? [])
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item.CharacterA) && !string.IsNullOrWhiteSpace(item.CharacterB))
|
||||
.Select(item => new StoryIntelligenceExperienceRelationshipState
|
||||
{
|
||||
@ -505,9 +537,26 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
State = Trim(FirstConfigured(item.Evidence, item.RelationshipSignal, "Emerging"), 58),
|
||||
Weight = ConfidenceWeight(item.Confidence, 70)
|
||||
})
|
||||
.OrderByDescending(item => item.Weight)
|
||||
.ToList();
|
||||
var seen = new HashSet<string>(current.Select(item => $"{item.SourceId}|{item.TargetId}"), StringComparer.OrdinalIgnoreCase);
|
||||
var memoryRelationships = (storyMemory?.Relationships ?? [])
|
||||
.Where(item => seen.Add($"{item.SourceId}|{item.TargetId}"))
|
||||
.Select(item => new StoryIntelligenceExperienceRelationshipState
|
||||
{
|
||||
Id = item.Id,
|
||||
SourceId = item.SourceId,
|
||||
TargetId = item.TargetId,
|
||||
Label = item.Label,
|
||||
State = item.State,
|
||||
Weight = Math.Max(44, item.Weight - 16)
|
||||
});
|
||||
|
||||
return current.Concat(memoryRelationships)
|
||||
.OrderByDescending(item => item.Weight)
|
||||
.Take(4)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<StoryIntelligenceExperienceTextItem> BuildKnowledge(SceneIntelligenceScene scene)
|
||||
{
|
||||
@ -733,6 +782,179 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
: "All known chapter runs are complete. Awaiting the next book import stage.";
|
||||
}
|
||||
|
||||
private sealed class StoryMemory
|
||||
{
|
||||
private readonly Dictionary<string, MemoryCharacter> characters = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, StoryIntelligenceExperienceRelationshipState> relationships = new(StringComparer.OrdinalIgnoreCase);
|
||||
private string? narratorResolvedName;
|
||||
|
||||
public IReadOnlyCollection<MemoryCharacter> Characters => characters.Values;
|
||||
public IReadOnlyCollection<StoryIntelligenceExperienceRelationshipState> Relationships => relationships.Values;
|
||||
public decimal NarratorConfidence { get; private set; }
|
||||
public int ResolvedCharacterCount => characters.Values.Count(character => character.IsResolved);
|
||||
public int UnresolvedCharacterCount => characters.Values.Count(character => !character.IsResolved);
|
||||
|
||||
public void Update(SceneIntelligenceScene scene)
|
||||
{
|
||||
var resolvedNarrator = ResolveNarratorName(scene);
|
||||
if (!string.IsNullOrWhiteSpace(resolvedNarrator))
|
||||
{
|
||||
narratorResolvedName = resolvedNarrator;
|
||||
NarratorConfidence = Math.Max(NarratorConfidence, 0.82m);
|
||||
}
|
||||
else if (IsGenericNarratorName(scene.PointOfView?.CharacterName))
|
||||
{
|
||||
NarratorConfidence = Math.Max(NarratorConfidence, 0.35m);
|
||||
}
|
||||
|
||||
foreach (var character in scene.Characters ?? [])
|
||||
{
|
||||
var name = Clean(character.Name);
|
||||
if (string.IsNullOrWhiteSpace(name) || IsGenericNarratorName(name))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(narratorResolvedName))
|
||||
{
|
||||
name = narratorResolvedName;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "Narrator";
|
||||
}
|
||||
}
|
||||
|
||||
var memory = GetOrAddCharacter(name);
|
||||
memory.Weight = Math.Max(memory.Weight, CharacterWeight(character));
|
||||
memory.Role = FirstConfigured(memory.Role, character.RoleInScene, character.MentionedOnly == true ? "Referenced" : "Scene character");
|
||||
memory.Relevance = Trim(FirstConfigured(character.Notes, memory.Relevance, string.Join(", ", character.Actions ?? []), "Remembered from earlier scenes"), 60);
|
||||
ApplyInference(memory, $"{name} {character.RoleInScene} {character.Notes} {string.Join(" ", character.Actions ?? [])}");
|
||||
}
|
||||
|
||||
var povName = ResolvePointOfView(scene);
|
||||
if (!string.IsNullOrWhiteSpace(povName))
|
||||
{
|
||||
var pov = GetOrAddCharacter(povName);
|
||||
pov.PovCount += 1;
|
||||
pov.Weight = Math.Max(pov.Weight, 100);
|
||||
pov.Role = "POV character";
|
||||
pov.Relevance = pov.PovCount > 1 ? "Recurring point of view" : "Point of view";
|
||||
}
|
||||
|
||||
foreach (var relationship in scene.Relationships ?? [])
|
||||
{
|
||||
var a = ResolveCharacterName(relationship.CharacterA);
|
||||
var b = ResolveCharacterName(relationship.CharacterB);
|
||||
if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyInference(GetOrAddCharacter(a), $"{relationship.RelationshipSignal} {relationship.Evidence}");
|
||||
ApplyInference(GetOrAddCharacter(b), $"{relationship.RelationshipSignal} {relationship.Evidence}");
|
||||
|
||||
var key = $"{CharacterId(a)}|{CharacterId(b)}";
|
||||
relationships[key] = new StoryIntelligenceExperienceRelationshipState
|
||||
{
|
||||
Id = $"memory-relationship-{StableKey(a)}-{StableKey(b)}",
|
||||
SourceId = CharacterId(a),
|
||||
TargetId = CharacterId(b),
|
||||
Label = Trim(FirstConfigured(relationship.RelationshipSignal, "Relationship remembered"), 44),
|
||||
State = Trim(FirstConfigured(relationship.Evidence, relationship.RelationshipSignal, "Carried forward from earlier scenes"), 58),
|
||||
Weight = ConfidenceWeight(relationship.Confidence, 64)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string? ResolvePointOfView(SceneIntelligenceScene scene)
|
||||
{
|
||||
var raw = Clean(scene.PointOfView?.CharacterName);
|
||||
if (IsGenericNarratorName(raw))
|
||||
{
|
||||
return narratorResolvedName ?? raw;
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(raw) ? narratorResolvedName : raw;
|
||||
}
|
||||
|
||||
private string ResolveCharacterName(string? raw)
|
||||
{
|
||||
var name = Clean(raw);
|
||||
return IsGenericNarratorName(name) && !string.IsNullOrWhiteSpace(narratorResolvedName) ? narratorResolvedName! : name;
|
||||
}
|
||||
|
||||
private MemoryCharacter GetOrAddCharacter(string name)
|
||||
{
|
||||
var key = CharacterId(name);
|
||||
if (!characters.TryGetValue(key, out var character))
|
||||
{
|
||||
character = new MemoryCharacter(name);
|
||||
characters[key] = character;
|
||||
}
|
||||
|
||||
return character;
|
||||
}
|
||||
|
||||
private static void ApplyInference(MemoryCharacter character, string evidence)
|
||||
{
|
||||
var text = evidence.ToLowerInvariant();
|
||||
if (ContainsAny(text, "mother", "daughter", "sister", "wife", "she ", " her "))
|
||||
{
|
||||
character.Presentation = "Feminine";
|
||||
}
|
||||
else if (ContainsAny(text, "father", "son", "brother", "husband", " he ", " him "))
|
||||
{
|
||||
character.Presentation = "Masculine";
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Presentation ??= NamePresentation(character.Name);
|
||||
}
|
||||
|
||||
if (ContainsAny(text, "grandfather", "grandmother", "elderly"))
|
||||
{
|
||||
character.AgeBand = "Elderly";
|
||||
}
|
||||
else if (ContainsAny(text, "mother", "father", "parent", "middle aged", "middle-aged"))
|
||||
{
|
||||
character.AgeBand = "Middle Aged";
|
||||
}
|
||||
else if (ContainsAny(text, "teenage", "teenager", "fifteen", "sixteen", "seventeen"))
|
||||
{
|
||||
character.AgeBand = "Older Teen";
|
||||
}
|
||||
}
|
||||
|
||||
private static string? NamePresentation(string name)
|
||||
{
|
||||
var key = name.ToLowerInvariant();
|
||||
if (key is "beth" or "rosie" or "maggie" or "annie" or "anna" or "anne")
|
||||
{
|
||||
return "Feminine";
|
||||
}
|
||||
|
||||
if (key is "graham" or "simon" or "kevin" or "colin" or "john" or "david")
|
||||
{
|
||||
return "Masculine";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ContainsAny(string text, params string[] needles)
|
||||
=> needles.Any(needle => text.Contains(needle, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private sealed class MemoryCharacter(string name)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public string Role { get; set; } = "Remembered character";
|
||||
public string Relevance { get; set; } = "Known from earlier scenes";
|
||||
public int Weight { get; set; } = 48;
|
||||
public int PovCount { get; set; }
|
||||
public string? Presentation { get; set; }
|
||||
public string? AgeBand { get; set; }
|
||||
public bool IsResolved => !IsGenericNarratorName(Name);
|
||||
}
|
||||
|
||||
private static string CurrentSceneText(RunSnapshot snapshot)
|
||||
{
|
||||
if (snapshot.Run.TotalDetectedScenes.HasValue)
|
||||
@ -773,7 +995,8 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
int parsedScenes,
|
||||
int totalPersistedResults,
|
||||
int totalScenes,
|
||||
int overallProgressPercent)
|
||||
int overallProgressPercent,
|
||||
StoryMemory storyMemory)
|
||||
{
|
||||
var failed = snapshots.Sum(snapshot => snapshot.Parsed.Count(item => item.Scene is null));
|
||||
var warning = failed == 0 ? null : $"{failed:N0} scene result(s) skipped because they could not be parsed.";
|
||||
@ -792,6 +1015,10 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
TotalScenes = totalScenes,
|
||||
ProgressSource = "ImportSession",
|
||||
CurrentBackendStage = BackendStageDisplay(activeSnapshot.Run),
|
||||
StoryMemoryCharacterCount = storyMemory.Characters.Count,
|
||||
ResolvedCharacterCount = storyMemory.ResolvedCharacterCount,
|
||||
UnresolvedCharacterCount = storyMemory.UnresolvedCharacterCount,
|
||||
NarratorConfidence = storyMemory.NarratorConfidence,
|
||||
SnapshotSource = "Real",
|
||||
RunStatus = SessionStatus(session, snapshots),
|
||||
PersistedCompletedSceneCount = snapshots.Sum(snapshot => snapshot.Run.CompletedScenes ?? snapshot.Completed.Count),
|
||||
@ -813,7 +1040,8 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
IReadOnlyList<StoryIntelligenceSavedSceneResult> results,
|
||||
IReadOnlyList<ParsedSceneResult> parsed,
|
||||
IReadOnlyList<ParsedSceneResult> completed,
|
||||
ParsedSceneResult? current)
|
||||
ParsedSceneResult? current,
|
||||
StoryMemory storyMemory)
|
||||
{
|
||||
var failed = parsed.Where(item => item.Scene is null).ToList();
|
||||
var warning = failed.Count == 0
|
||||
@ -834,6 +1062,10 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
TotalScenes = Math.Max(run.TotalDetectedScenes ?? 0, results.Count),
|
||||
ProgressSource = "ChapterRun",
|
||||
CurrentBackendStage = BackendStageDisplay(run),
|
||||
StoryMemoryCharacterCount = storyMemory.Characters.Count,
|
||||
ResolvedCharacterCount = storyMemory.ResolvedCharacterCount,
|
||||
UnresolvedCharacterCount = storyMemory.UnresolvedCharacterCount,
|
||||
NarratorConfidence = storyMemory.NarratorConfidence,
|
||||
SnapshotSource = "Real",
|
||||
RunStatus = run.Status,
|
||||
PersistedCompletedSceneCount = run.CompletedScenes ?? 0,
|
||||
@ -884,6 +1116,20 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyIllustrationDiagnostics(StoryIntelligenceExperiencePrototypeViewModel model)
|
||||
{
|
||||
var characterResolutions = model.Scenes
|
||||
.SelectMany(scene => scene.Characters)
|
||||
.Select(character => character.ImageResolution)
|
||||
.Where(resolution => resolution.IllustrationLibraryItemId.HasValue)
|
||||
.ToList();
|
||||
model.Diagnostics.IllustrationAssignmentCount = characterResolutions
|
||||
.Select(resolution => resolution.IllustrationLibraryItemId!.Value)
|
||||
.Distinct()
|
||||
.Count();
|
||||
model.Diagnostics.IllustrationChangeCount = characterResolutions.Count(resolution => !string.IsNullOrWhiteSpace(resolution.ReasonReassigned));
|
||||
}
|
||||
|
||||
private static bool IsTerminal(string status)
|
||||
=> status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings or StoryIntelligenceRunStatuses.Failed or StoryIntelligenceRunStatuses.Cancelled;
|
||||
|
||||
@ -1102,6 +1348,9 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
||||
private static string LocationLibraryCode(string name, SceneIntelligenceLocation? location, SceneIntelligenceSetting? setting)
|
||||
{
|
||||
var value = $"{name} {location?.LocationType} {location?.GenericRoomType} {setting?.LocationType}".ToLowerInvariant();
|
||||
if (value.Contains("bathroom") || value.Contains("toilet") || value.Contains("washroom")) return "loc-laundry-utility-room";
|
||||
if (value.Contains("kitchen") || value.Contains("desk") || value.Contains("office") || value.Contains("study") || value.Contains("library")) return "loc-domestic-desk";
|
||||
if (value.Contains("waiting room") || value.Contains("bedroom") || value.Contains("sitting room") || value.Contains("living room")) return "loc-small-flat-interior";
|
||||
if (value.Contains("laundry") || value.Contains("utility")) return "loc-laundry-utility-room";
|
||||
if (value.Contains("stair")) return "loc-narrow-stairwell";
|
||||
if (value.Contains("car park") || value.Contains("parking")) return "loc-wet-car-park";
|
||||
|
||||
@ -32,6 +32,12 @@ public sealed class StoryIntelligenceExperienceSnapshotDiagnostics
|
||||
public int TotalScenes { get; init; }
|
||||
public string ProgressSource { get; init; } = string.Empty;
|
||||
public string CurrentBackendStage { get; init; } = string.Empty;
|
||||
public int StoryMemoryCharacterCount { get; init; }
|
||||
public int ResolvedCharacterCount { get; init; }
|
||||
public int UnresolvedCharacterCount { get; init; }
|
||||
public decimal NarratorConfidence { get; init; }
|
||||
public int IllustrationAssignmentCount { get; set; }
|
||||
public int IllustrationChangeCount { get; set; }
|
||||
public string SnapshotSource { get; init; } = string.Empty;
|
||||
public string RunStatus { get; init; } = string.Empty;
|
||||
public int PersistedCompletedSceneCount { get; init; }
|
||||
|
||||
@ -237,6 +237,30 @@
|
||||
<dt>Current backend stage</dt>
|
||||
<dd data-diagnostic-key="currentBackendStage">@Model.Diagnostics.CurrentBackendStage</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Story Memory character count</dt>
|
||||
<dd data-diagnostic-key="storyMemoryCharacterCount">@Model.Diagnostics.StoryMemoryCharacterCount</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Resolved characters</dt>
|
||||
<dd data-diagnostic-key="resolvedCharacterCount">@Model.Diagnostics.ResolvedCharacterCount</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Unresolved characters</dt>
|
||||
<dd data-diagnostic-key="unresolvedCharacterCount">@Model.Diagnostics.UnresolvedCharacterCount</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Narrator confidence</dt>
|
||||
<dd data-diagnostic-key="narratorConfidence">@Model.Diagnostics.NarratorConfidence</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Illustration assignments</dt>
|
||||
<dd data-diagnostic-key="illustrationAssignmentCount">@Model.Diagnostics.IllustrationAssignmentCount</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Illustration changes</dt>
|
||||
<dd data-diagnostic-key="illustrationChangeCount">@Model.Diagnostics.IllustrationChangeCount</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Current polling interval</dt>
|
||||
<dd data-diagnostic-key="currentPollingInterval">Initial</dd>
|
||||
|
||||
@ -781,7 +781,7 @@ input:focus-visible {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 2px;
|
||||
width: min(176px, calc(var(--node-size, 150px) + 44px));
|
||||
width: min(188px, calc(var(--node-size, 150px) + 58px));
|
||||
max-width: calc(100vw - 32px);
|
||||
margin-left: 0;
|
||||
border: 1px solid rgba(151, 185, 218, 0.1);
|
||||
@ -831,23 +831,27 @@ input:focus-visible {
|
||||
}
|
||||
|
||||
.story-character__text strong {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
font-size: clamp(0.82rem, 0.9vw, 0.98rem);
|
||||
line-height: 1.05;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.story-character__text span {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
color: var(--story-muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
|
||||
.story-character.is-context .story-character__text {
|
||||
|
||||
@ -193,6 +193,7 @@
|
||||
function stageIndex(scene) {
|
||||
const stage = String(scene.stage || "").toLowerCase();
|
||||
if (scene.progressPercent >= 100 || stage.includes("review")) return analysisStages.length - 1;
|
||||
if (stage.includes("final")) return 10;
|
||||
if (stage.includes("continuity")) return 9;
|
||||
if (stage.includes("knowledge")) return 8;
|
||||
if (stage.includes("relationship")) return 7;
|
||||
@ -202,6 +203,7 @@
|
||||
if (stage.includes("content") || stage.includes("inferring")) return 3;
|
||||
if (stage.includes("scene")) return 2;
|
||||
if (stage.includes("chapter")) return 1;
|
||||
if (stage.includes("reading") || stage.includes("read ")) return 0;
|
||||
return Math.max(0, Math.min(analysisStages.length - 1, Math.floor(scene.progressPercent / 10)));
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user