diff --git a/PlotLine/Models/StoryIntelligencePersistenceModels.cs b/PlotLine/Models/StoryIntelligencePersistenceModels.cs index cdac7fa..7919857 100644 --- a/PlotLine/Models/StoryIntelligencePersistenceModels.cs +++ b/PlotLine/Models/StoryIntelligencePersistenceModels.cs @@ -154,6 +154,7 @@ public sealed class StoryIntelligenceSavedRun public decimal? ChapterNumber { get; init; } public string Status { get; init; } = string.Empty; public string SourceType { get; init; } = string.Empty; + public string? SourceText { get; init; } public string? SourceFileName { get; init; } public long? SourceFileSizeBytes { get; init; } public int? SourceWordCount { get; init; } diff --git a/PlotLine/Services/StoryIntelligenceParagraphs.cs b/PlotLine/Services/StoryIntelligenceParagraphs.cs index 4fdfaee..8f3abcb 100644 --- a/PlotLine/Services/StoryIntelligenceParagraphs.cs +++ b/PlotLine/Services/StoryIntelligenceParagraphs.cs @@ -5,8 +5,8 @@ namespace PlotLine.Services; public static class StoryIntelligenceParagraphs { public static IReadOnlyList Split(string? chapterText) - => (chapterText ?? string.Empty) - .Split(["\r\n\r\n", "\n\n", "\r\r"], StringSplitOptions.RemoveEmptyEntries) + => NormalizeLineEndings(chapterText) + .Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(paragraph => paragraph.Trim()) .Where(paragraph => !string.IsNullOrWhiteSpace(paragraph)) .ToList(); @@ -30,4 +30,13 @@ public static class StoryIntelligenceParagraphs return builder.ToString(); } + + private static string NormalizeLineEndings(string? value) + => (value ?? string.Empty) + .Replace("\r\n", "\n", StringComparison.Ordinal) + .Replace('\r', '\n') + .Replace('\v', '\n') + .Replace('\f', '\n') + .Replace('\u2028', '\n') + .Replace('\u2029', '\n'); } diff --git a/PlotLine/Sql/120_Phase20R_DeterministicChapterParagraphs.sql b/PlotLine/Sql/120_Phase20R_DeterministicChapterParagraphs.sql index 65c8817..ba8a179 100644 --- a/PlotLine/Sql/120_Phase20R_DeterministicChapterParagraphs.sql +++ b/PlotLine/Sql/120_Phase20R_DeterministicChapterParagraphs.sql @@ -61,6 +61,7 @@ BEGIN r.ChapterNumber, r.Status, r.SourceType, + r.SourceText, r.SourceFileName, r.SourceFileSizeBytes, r.SourceWordCount, diff --git a/PlotLine/Views/Admin/StoryIntelligenceRunDetails.cshtml b/PlotLine/Views/Admin/StoryIntelligenceRunDetails.cshtml index a69316c..bc0695f 100644 --- a/PlotLine/Views/Admin/StoryIntelligenceRunDetails.cshtml +++ b/PlotLine/Views/Admin/StoryIntelligenceRunDetails.cshtml @@ -95,6 +95,10 @@ else
@Display(run.SourceCharacterCount)
Submitted paragraph count
@Display(run.SourceParagraphCount)
+
First five numbered paragraphs
+
@FirstNumberedParagraphs(run)
+
Last paragraph number
+
@Display(LastParagraphNumber(run))
Source chapter count
@Display(run.SourceChapterCount)
Detected document features
@@ -274,6 +278,25 @@ else : $"Chapter ID {chapterId.Value:N0}" : "None"; + private static string FirstNumberedParagraphs(StoryIntelligenceSavedRun run) + { + var paragraphs = StoryIntelligenceParagraphs.Split(run.SourceText); + if (paragraphs.Count == 0) + { + return "None"; + } + + return string.Join( + Environment.NewLine + Environment.NewLine, + paragraphs.Take(5).Select((paragraph, index) => $"[{index + 1}] {PreviewParagraph(paragraph)}")); + } + + private static int? LastParagraphNumber(StoryIntelligenceSavedRun run) + { + var paragraphs = StoryIntelligenceParagraphs.Split(run.SourceText); + return paragraphs.Count == 0 ? null : paragraphs.Count; + } + private static string ChapterStructureStatus(StoryIntelligenceSavedChapterResult? chapter, StoryIntelligenceSavedRun run) { if (chapter is not null) @@ -368,4 +391,10 @@ else return []; } } + + private static string PreviewParagraph(string value) + { + var cleaned = value.ReplaceLineEndings(" ").Trim(); + return cleaned.Length <= 220 ? cleaned : $"{cleaned[..220]}..."; + } }