Phase 20R.1
This commit is contained in:
parent
ebeca1e3e1
commit
f728168173
@ -172,6 +172,7 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn
|
|||||||
request.SourceFileSizeBytes,
|
request.SourceFileSizeBytes,
|
||||||
request.SourceWordCount,
|
request.SourceWordCount,
|
||||||
request.SourceCharacterCount,
|
request.SourceCharacterCount,
|
||||||
|
request.SourceParagraphCount,
|
||||||
request.SourceChapterCount,
|
request.SourceChapterCount,
|
||||||
request.Model,
|
request.Model,
|
||||||
request.PromptVersionsSummary
|
request.PromptVersionsSummary
|
||||||
|
|||||||
@ -37,11 +37,23 @@ Do not create boundaries for ordinary paragraph changes, small action beats, dia
|
|||||||
|
|
||||||
## Paragraph Numbering
|
## Paragraph Numbering
|
||||||
|
|
||||||
Treat the supplied chapter text as a sequence of non-empty paragraphs.
|
The supplied chapter text is already split into numbered paragraphs.
|
||||||
|
|
||||||
The first non-empty paragraph is paragraph `1`.
|
Each paragraph begins with an explicit marker such as:
|
||||||
|
|
||||||
Each `startParagraph` and `endParagraph` must refer to these paragraph numbers.
|
```text
|
||||||
|
[1] First paragraph text...
|
||||||
|
[2] Second paragraph text...
|
||||||
|
[3] Third paragraph text...
|
||||||
|
```
|
||||||
|
|
||||||
|
Use only the supplied paragraph numbers.
|
||||||
|
|
||||||
|
Each `startParagraph` and `endParagraph` must refer to those explicit paragraph numbers.
|
||||||
|
|
||||||
|
Never invent paragraph numbers.
|
||||||
|
|
||||||
|
Never return an `endParagraph` greater than the highest paragraph number supplied.
|
||||||
|
|
||||||
Scene boundaries must:
|
Scene boundaries must:
|
||||||
|
|
||||||
@ -49,6 +61,7 @@ Scene boundaries must:
|
|||||||
- cover the chapter in order;
|
- cover the chapter in order;
|
||||||
- not overlap;
|
- not overlap;
|
||||||
- not leave gaps;
|
- not leave gaps;
|
||||||
|
- be contiguous and non-overlapping where possible;
|
||||||
- use increasing `sceneNumber` values starting at `1`.
|
- use increasing `sceneNumber` values starting at `1`.
|
||||||
|
|
||||||
## Output Requirements
|
## Output Requirements
|
||||||
|
|||||||
@ -32,6 +32,7 @@ public sealed class StoryIntelligenceRunSaveRequest
|
|||||||
public long? SourceFileSizeBytes { get; init; }
|
public long? SourceFileSizeBytes { get; init; }
|
||||||
public int? SourceWordCount { get; init; }
|
public int? SourceWordCount { get; init; }
|
||||||
public int? SourceCharacterCount { get; init; }
|
public int? SourceCharacterCount { get; init; }
|
||||||
|
public int? SourceParagraphCount { get; init; }
|
||||||
public int? SourceChapterCount { get; init; }
|
public int? SourceChapterCount { get; init; }
|
||||||
public int? SourceDetectedImagesCount { get; init; }
|
public int? SourceDetectedImagesCount { get; init; }
|
||||||
public int? SourceDetectedTablesCount { get; init; }
|
public int? SourceDetectedTablesCount { get; init; }
|
||||||
@ -69,6 +70,7 @@ public sealed class StoryIntelligenceRunQueueRequest
|
|||||||
public long? SourceFileSizeBytes { get; init; }
|
public long? SourceFileSizeBytes { get; init; }
|
||||||
public int? SourceWordCount { get; init; }
|
public int? SourceWordCount { get; init; }
|
||||||
public int? SourceCharacterCount { get; init; }
|
public int? SourceCharacterCount { get; init; }
|
||||||
|
public int? SourceParagraphCount { get; init; }
|
||||||
public int? SourceChapterCount { get; init; }
|
public int? SourceChapterCount { get; init; }
|
||||||
public string Model { get; init; } = string.Empty;
|
public string Model { get; init; } = string.Empty;
|
||||||
public string PromptVersionsSummary { get; init; } = string.Empty;
|
public string PromptVersionsSummary { get; init; } = string.Empty;
|
||||||
@ -156,6 +158,7 @@ public sealed class StoryIntelligenceSavedRun
|
|||||||
public long? SourceFileSizeBytes { get; init; }
|
public long? SourceFileSizeBytes { get; init; }
|
||||||
public int? SourceWordCount { get; init; }
|
public int? SourceWordCount { get; init; }
|
||||||
public int? SourceCharacterCount { get; init; }
|
public int? SourceCharacterCount { get; init; }
|
||||||
|
public int? SourceParagraphCount { get; init; }
|
||||||
public int? SourceChapterCount { get; init; }
|
public int? SourceChapterCount { get; init; }
|
||||||
public int? SourceDetectedImagesCount { get; init; }
|
public int? SourceDetectedImagesCount { get; init; }
|
||||||
public int? SourceDetectedTablesCount { get; init; }
|
public int? SourceDetectedTablesCount { get; init; }
|
||||||
@ -200,6 +203,7 @@ public sealed class StoryIntelligenceQueuedRun
|
|||||||
public string? SourceText { get; init; }
|
public string? SourceText { get; init; }
|
||||||
public int? SourceWordCount { get; init; }
|
public int? SourceWordCount { get; init; }
|
||||||
public int? SourceCharacterCount { get; init; }
|
public int? SourceCharacterCount { get; init; }
|
||||||
|
public int? SourceParagraphCount { get; init; }
|
||||||
public int? SourceChapterCount { get; init; }
|
public int? SourceChapterCount { get; init; }
|
||||||
public string PromptVersion { get; init; } = string.Empty;
|
public string PromptVersion { get; init; } = string.Empty;
|
||||||
public string? PromptVersionsSummary { get; init; }
|
public string? PromptVersionsSummary { get; init; }
|
||||||
|
|||||||
@ -39,7 +39,7 @@ public sealed class ChapterStoryIntelligenceDryRunService(
|
|||||||
var chapterPromptVersion = versions.GetPromptVersion(ChapterPromptFile);
|
var chapterPromptVersion = versions.GetPromptVersion(ChapterPromptFile);
|
||||||
var scenePromptVersion = versions.GetPromptVersion(ScenePromptFile);
|
var scenePromptVersion = versions.GetPromptVersion(ScenePromptFile);
|
||||||
var clientStatus = client.GetConfigurationStatus();
|
var clientStatus = client.GetConfigurationStatus();
|
||||||
var paragraphs = SplitParagraphs(form.ChapterText);
|
var paragraphs = StoryIntelligenceParagraphs.Split(form.ChapterText);
|
||||||
var chapterContextJson = BuildChapterContextJson(form);
|
var chapterContextJson = BuildChapterContextJson(form);
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -50,7 +50,8 @@ public sealed class ChapterStoryIntelligenceDryRunService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var chapterTemplate = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
|
var chapterTemplate = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
|
||||||
var chapterPrompt = BuildChapterPrompt(chapterTemplate, chapterContextJson, form.ChapterText);
|
var numberedChapterText = StoryIntelligenceParagraphs.Number(paragraphs);
|
||||||
|
var chapterPrompt = BuildChapterPrompt(chapterTemplate, chapterContextJson, numberedChapterText);
|
||||||
var chapterResult = await client.ExecutePromptAsync(chapterPrompt, chapterPromptVersion, cancellationToken);
|
var chapterResult = await client.ExecutePromptAsync(chapterPrompt, chapterPromptVersion, cancellationToken);
|
||||||
var chapterJson = ExtractOutputText(chapterResult.RawResponseText);
|
var chapterJson = ExtractOutputText(chapterResult.RawResponseText);
|
||||||
var parsedChapter = JsonSerializer.Deserialize<ChapterStructureModel>(chapterJson, JsonOptions);
|
var parsedChapter = JsonSerializer.Deserialize<ChapterStructureModel>(chapterJson, JsonOptions);
|
||||||
@ -313,12 +314,6 @@ public sealed class ChapterStoryIntelligenceDryRunService(
|
|||||||
return outputText.Trim();
|
return outputText.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> SplitParagraphs(string chapterText)
|
|
||||||
=> chapterText.Split(["\r\n\r\n", "\n\n", "\r\r"], StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(paragraph => paragraph.Trim())
|
|
||||||
.Where(paragraph => !string.IsNullOrWhiteSpace(paragraph))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
private static string Preview(string value)
|
private static string Preview(string value)
|
||||||
{
|
{
|
||||||
var cleaned = value.ReplaceLineEndings(" ").Trim();
|
var cleaned = value.ReplaceLineEndings(" ").Trim();
|
||||||
|
|||||||
@ -66,7 +66,7 @@ public sealed class PersistedStoryIntelligenceRunner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ThrowIfCancellationRequestedAsync(run.StoryIntelligenceRunID, stopwatch.ElapsedMilliseconds, cancellationToken);
|
await ThrowIfCancellationRequestedAsync(run.StoryIntelligenceRunID, stopwatch.ElapsedMilliseconds, cancellationToken);
|
||||||
var paragraphs = SplitParagraphs(run.SourceText);
|
var paragraphs = StoryIntelligenceParagraphs.Split(run.SourceText);
|
||||||
if (paragraphs.Count == 0)
|
if (paragraphs.Count == 0)
|
||||||
{
|
{
|
||||||
throw new StoryIntelligenceRunFailureException(
|
throw new StoryIntelligenceRunFailureException(
|
||||||
@ -76,7 +76,8 @@ public sealed class PersistedStoryIntelligenceRunner(
|
|||||||
|
|
||||||
var chapterContextJson = BuildChapterContextJson(run);
|
var chapterContextJson = BuildChapterContextJson(run);
|
||||||
var chapterTemplate = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
|
var chapterTemplate = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
|
||||||
var chapterPrompt = BuildChapterPrompt(chapterTemplate, chapterContextJson, run.SourceText);
|
var numberedChapterText = StoryIntelligenceParagraphs.Number(paragraphs);
|
||||||
|
var chapterPrompt = BuildChapterPrompt(chapterTemplate, chapterContextJson, numberedChapterText);
|
||||||
currentFailureStage = StoryIntelligenceFailureStages.ChapterStructure;
|
currentFailureStage = StoryIntelligenceFailureStages.ChapterStructure;
|
||||||
|
|
||||||
await repository.UpdateProgressAsync(
|
await repository.UpdateProgressAsync(
|
||||||
@ -386,12 +387,6 @@ public sealed class PersistedStoryIntelligenceRunner(
|
|||||||
: outputText.Trim();
|
: outputText.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> SplitParagraphs(string chapterText)
|
|
||||||
=> chapterText.Split(["\r\n\r\n", "\n\n", "\r\r"], StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(paragraph => paragraph.Trim())
|
|
||||||
.Where(paragraph => !string.IsNullOrWhiteSpace(paragraph))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
private static string SerializeParsed<T>(T? value)
|
private static string SerializeParsed<T>(T? value)
|
||||||
=> value is null ? string.Empty : JsonSerializer.Serialize(value, JsonOptions);
|
=> value is null ? string.Empty : JsonSerializer.Serialize(value, JsonOptions);
|
||||||
|
|
||||||
|
|||||||
@ -70,6 +70,7 @@ public sealed class StoryIntelligenceExistingChapterQueueService(
|
|||||||
ChapterText = sourceText,
|
ChapterText = sourceText,
|
||||||
SourceWordCount = selected.SourceWordCount ?? CountWords(sourceText),
|
SourceWordCount = selected.SourceWordCount ?? CountWords(sourceText),
|
||||||
SourceCharacterCount = selected.SourceCharacterCount ?? sourceText.Length,
|
SourceCharacterCount = selected.SourceCharacterCount ?? sourceText.Length,
|
||||||
|
SourceParagraphCount = StoryIntelligenceParagraphs.Split(sourceText).Count,
|
||||||
SourceChapterCount = 1,
|
SourceChapterCount = 1,
|
||||||
Model = clientStatus.Model,
|
Model = clientStatus.Model,
|
||||||
PromptVersionsSummary = "Chapter=Chapter-Structure-Prompt-V1; Scene=Scene-Prompt-V1"
|
PromptVersionsSummary = "Chapter=Chapter-Structure-Prompt-V1; Scene=Scene-Prompt-V1"
|
||||||
|
|||||||
@ -73,6 +73,7 @@ public sealed class StoryIntelligenceFullChapterTestService(
|
|||||||
SourceFileSizeBytes = source.SourceFileSizeBytes,
|
SourceFileSizeBytes = source.SourceFileSizeBytes,
|
||||||
SourceWordCount = preflight.WordCount,
|
SourceWordCount = preflight.WordCount,
|
||||||
SourceCharacterCount = preflight.CharacterCount,
|
SourceCharacterCount = preflight.CharacterCount,
|
||||||
|
SourceParagraphCount = preflight.ParagraphCount,
|
||||||
SourceChapterCount = 1,
|
SourceChapterCount = 1,
|
||||||
Model = clientStatus.Model,
|
Model = clientStatus.Model,
|
||||||
PromptVersionsSummary = PromptVersionsSummary
|
PromptVersionsSummary = PromptVersionsSummary
|
||||||
@ -117,7 +118,7 @@ public sealed class StoryIntelligenceFullChapterTestService(
|
|||||||
{
|
{
|
||||||
var characterCount = text.Length;
|
var characterCount = text.Length;
|
||||||
var wordCount = CountWords(text);
|
var wordCount = CountWords(text);
|
||||||
var paragraphCount = SplitParagraphs(text).Count;
|
var paragraphCount = StoryIntelligenceParagraphs.Split(text).Count;
|
||||||
var estimatedInputTokens = EstimateTokens(text);
|
var estimatedInputTokens = EstimateTokens(text);
|
||||||
var estimatedPipelineInputTokens = estimatedInputTokens * 2;
|
var estimatedPipelineInputTokens = estimatedInputTokens * 2;
|
||||||
var warnings = BuildWarnings(text, characterCount, paragraphCount);
|
var warnings = BuildWarnings(text, characterCount, paragraphCount);
|
||||||
@ -211,12 +212,6 @@ public sealed class StoryIntelligenceFullChapterTestService(
|
|||||||
? 0
|
? 0
|
||||||
: value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
|
: value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
|
||||||
|
|
||||||
private static IReadOnlyList<string> SplitParagraphs(string value)
|
|
||||||
=> value.Split(["\r\n\r\n", "\n\n", "\r\r"], StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Select(paragraph => paragraph.Trim())
|
|
||||||
.Where(paragraph => !string.IsNullOrWhiteSpace(paragraph))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
private static int EstimateTokens(string value)
|
private static int EstimateTokens(string value)
|
||||||
=> Math.Max(1, (int)Math.Ceiling(value.Length / 4m));
|
=> Math.Max(1, (int)Math.Ceiling(value.Length / 4m));
|
||||||
|
|
||||||
|
|||||||
@ -517,7 +517,8 @@ public sealed class ChapterStructureDryRunService(
|
|||||||
var promptVersion = versions.GetPromptVersion(ChapterPromptFile);
|
var promptVersion = versions.GetPromptVersion(ChapterPromptFile);
|
||||||
var clientStatus = client.GetConfigurationStatus();
|
var clientStatus = client.GetConfigurationStatus();
|
||||||
var contextJson = BuildChapterContextJson(form);
|
var contextJson = BuildChapterContextJson(form);
|
||||||
var paragraphCount = CountParagraphs(form.ChapterText);
|
var paragraphs = StoryIntelligenceParagraphs.Split(form.ChapterText);
|
||||||
|
var paragraphCount = paragraphs.Count;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -527,7 +528,8 @@ public sealed class ChapterStructureDryRunService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var template = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
|
var template = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
|
||||||
var completedPrompt = BuildChapterPrompt(template, contextJson, form.ChapterText);
|
var numberedChapterText = StoryIntelligenceParagraphs.Number(paragraphs);
|
||||||
|
var completedPrompt = BuildChapterPrompt(template, contextJson, numberedChapterText);
|
||||||
var result = await client.ExecutePromptAsync(completedPrompt, promptVersion, cancellationToken);
|
var result = await client.ExecutePromptAsync(completedPrompt, promptVersion, cancellationToken);
|
||||||
var chapterJson = ExtractOutputText(result.RawResponseText);
|
var chapterJson = ExtractOutputText(result.RawResponseText);
|
||||||
var parsedChapter = DeserializeChapter(chapterJson);
|
var parsedChapter = DeserializeChapter(chapterJson);
|
||||||
@ -649,10 +651,6 @@ public sealed class ChapterStructureDryRunService(
|
|||||||
private static ChapterStructureModel? DeserializeChapter(string chapterJson)
|
private static ChapterStructureModel? DeserializeChapter(string chapterJson)
|
||||||
=> JsonSerializer.Deserialize<ChapterStructureModel>(chapterJson, JsonOptions);
|
=> JsonSerializer.Deserialize<ChapterStructureModel>(chapterJson, JsonOptions);
|
||||||
|
|
||||||
private static int CountParagraphs(string chapterText)
|
|
||||||
=> chapterText.Split(["\r\n\r\n", "\n\n", "\r\r"], StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Count(paragraph => !string.IsNullOrWhiteSpace(paragraph));
|
|
||||||
|
|
||||||
private static string FormatDuration(TimeSpan duration)
|
private static string FormatDuration(TimeSpan duration)
|
||||||
=> duration.TotalSeconds < 1
|
=> duration.TotalSeconds < 1
|
||||||
? $"{duration.TotalMilliseconds:0} ms"
|
? $"{duration.TotalMilliseconds:0} ms"
|
||||||
|
|||||||
33
PlotLine/Services/StoryIntelligenceParagraphs.cs
Normal file
33
PlotLine/Services/StoryIntelligenceParagraphs.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PlotLine.Services;
|
||||||
|
|
||||||
|
public static class StoryIntelligenceParagraphs
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<string> Split(string? chapterText)
|
||||||
|
=> (chapterText ?? string.Empty)
|
||||||
|
.Split(["\r\n\r\n", "\n\n", "\r\r"], StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(paragraph => paragraph.Trim())
|
||||||
|
.Where(paragraph => !string.IsNullOrWhiteSpace(paragraph))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
public static string Number(IReadOnlyList<string> paragraphs)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (var index = 0; index < paragraphs.Count; index++)
|
||||||
|
{
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append('[')
|
||||||
|
.Append(index + 1)
|
||||||
|
.Append("] ")
|
||||||
|
.Append(paragraphs[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -81,6 +81,7 @@ public sealed class StoryIntelligenceResultPersistenceService(
|
|||||||
ChapterText = form.ChapterText,
|
ChapterText = form.ChapterText,
|
||||||
SourceWordCount = CountWords(form.ChapterText),
|
SourceWordCount = CountWords(form.ChapterText),
|
||||||
SourceCharacterCount = form.ChapterText.Length,
|
SourceCharacterCount = form.ChapterText.Length,
|
||||||
|
SourceParagraphCount = StoryIntelligenceParagraphs.Split(form.ChapterText).Count,
|
||||||
SourceChapterCount = 1,
|
SourceChapterCount = 1,
|
||||||
Model = clientStatus.Model,
|
Model = clientStatus.Model,
|
||||||
PromptVersionsSummary = promptVersionsSummary
|
PromptVersionsSummary = promptVersionsSummary
|
||||||
|
|||||||
102
PlotLine/Sql/120_Phase20R_DeterministicChapterParagraphs.sql
Normal file
102
PlotLine/Sql/120_Phase20R_DeterministicChapterParagraphs.sql
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceParagraphCount') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceParagraphCount int NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_QueueAdminText
|
||||||
|
@UserID int,
|
||||||
|
@ProjectID int = NULL,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@ChapterID int = NULL,
|
||||||
|
@ChapterNumber decimal(9, 2) = NULL,
|
||||||
|
@SourceType nvarchar(50),
|
||||||
|
@SourceLabel nvarchar(300),
|
||||||
|
@SourceText nvarchar(max),
|
||||||
|
@SourceFileName nvarchar(260) = NULL,
|
||||||
|
@SourceFileSizeBytes bigint = NULL,
|
||||||
|
@SourceWordCount int = NULL,
|
||||||
|
@SourceCharacterCount int = NULL,
|
||||||
|
@SourceParagraphCount int = NULL,
|
||||||
|
@SourceChapterCount int = NULL,
|
||||||
|
@Model nvarchar(100),
|
||||||
|
@PromptVersionsSummary nvarchar(500)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
INSERT dbo.StoryIntelligenceRuns
|
||||||
|
(
|
||||||
|
UserID, ProjectID, BookID, ChapterID, ChapterNumber, Status, SourceType, SourceLabel, SourceText,
|
||||||
|
SourceFileName, SourceFileSizeBytes, SourceWordCount, SourceCharacterCount, SourceParagraphCount,
|
||||||
|
SourceChapterCount, Model, PromptVersion, PromptVersionsSummary, StartedUtc, CurrentStage, CurrentMessage,
|
||||||
|
CompletedScenes, FailedScenes
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@UserID, @ProjectID, @BookID, @ChapterID, @ChapterNumber, N'Pending', @SourceType, @SourceLabel, @SourceText,
|
||||||
|
@SourceFileName, @SourceFileSizeBytes, @SourceWordCount, @SourceCharacterCount, @SourceParagraphCount,
|
||||||
|
@SourceChapterCount, @Model, @PromptVersionsSummary, @PromptVersionsSummary, SYSUTCDATETIME(), N'Pending',
|
||||||
|
N'Queued for Story Intelligence processing.', 0, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int) AS StoryIntelligenceRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_GetAdmin
|
||||||
|
@StoryIntelligenceRunID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
r.StoryIntelligenceRunID,
|
||||||
|
r.UserID,
|
||||||
|
r.ProjectID,
|
||||||
|
p.ProjectName AS ProjectTitle,
|
||||||
|
r.BookID,
|
||||||
|
b.BookTitle,
|
||||||
|
r.ChapterID,
|
||||||
|
r.ChapterNumber,
|
||||||
|
r.Status,
|
||||||
|
r.SourceType,
|
||||||
|
r.SourceFileName,
|
||||||
|
r.SourceFileSizeBytes,
|
||||||
|
r.SourceWordCount,
|
||||||
|
r.SourceCharacterCount,
|
||||||
|
r.SourceParagraphCount,
|
||||||
|
r.SourceChapterCount,
|
||||||
|
r.SourceDetectedImagesCount,
|
||||||
|
r.SourceDetectedTablesCount,
|
||||||
|
r.SourceDetectedFootnotesCount,
|
||||||
|
r.SourceDetectedCommentsCount,
|
||||||
|
r.CurrentStage,
|
||||||
|
r.CurrentMessage,
|
||||||
|
r.TotalDetectedScenes,
|
||||||
|
r.CompletedScenes,
|
||||||
|
r.FailedScenes,
|
||||||
|
r.CancellationRequestedUtc,
|
||||||
|
r.CancelledUtc,
|
||||||
|
r.PromptVersion,
|
||||||
|
r.PromptVersionsSummary,
|
||||||
|
r.Model,
|
||||||
|
r.StartedUtc,
|
||||||
|
r.CompletedUtc,
|
||||||
|
r.FailureStage,
|
||||||
|
r.TotalInputTokens,
|
||||||
|
r.TotalOutputTokens,
|
||||||
|
r.TotalTokens,
|
||||||
|
r.TotalDurationMs,
|
||||||
|
r.EstimatedCostGBP,
|
||||||
|
r.EstimatedCostUSD,
|
||||||
|
r.ErrorMessage,
|
||||||
|
r.ErrorDetail,
|
||||||
|
r.CreatedUtc,
|
||||||
|
r.UpdatedUtc
|
||||||
|
FROM dbo.StoryIntelligenceRuns r
|
||||||
|
LEFT JOIN dbo.Projects p ON p.ProjectID = r.ProjectID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = r.BookID
|
||||||
|
WHERE r.StoryIntelligenceRunID = @StoryIntelligenceRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
@ -93,6 +93,8 @@ else
|
|||||||
<dd>@Display(run.SourceWordCount)</dd>
|
<dd>@Display(run.SourceWordCount)</dd>
|
||||||
<dt>Source character count</dt>
|
<dt>Source character count</dt>
|
||||||
<dd>@Display(run.SourceCharacterCount)</dd>
|
<dd>@Display(run.SourceCharacterCount)</dd>
|
||||||
|
<dt>Submitted paragraph count</dt>
|
||||||
|
<dd>@Display(run.SourceParagraphCount)</dd>
|
||||||
<dt>Source chapter count</dt>
|
<dt>Source chapter count</dt>
|
||||||
<dd>@Display(run.SourceChapterCount)</dd>
|
<dd>@Display(run.SourceChapterCount)</dd>
|
||||||
<dt>Detected document features</dt>
|
<dt>Detected document features</dt>
|
||||||
@ -169,6 +171,18 @@ else
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<label class="form-label" for="chapterParsedJson">Parsed JSON</label>
|
<label class="form-label" for="chapterParsedJson">Parsed JSON</label>
|
||||||
|
<dl>
|
||||||
|
<dt>Highest returned endParagraph</dt>
|
||||||
|
<dd>@Display(HighestReturnedEndParagraph(chapter))</dd>
|
||||||
|
<dt>Returned scene boundary ranges</dt>
|
||||||
|
<dd>@DisplayBoundaryRanges(chapter)</dd>
|
||||||
|
</dl>
|
||||||
|
@if (ReturnedEndParagraphExceedsSubmitted(chapter, run))
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
The model returned paragraph numbers beyond the submitted chapter. This usually means the prompt was not using explicit numbered paragraphs.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<textarea class="form-control font-monospace" id="chapterParsedJson" rows="10" readonly>@chapter.ParsedJson</textarea>
|
<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>
|
<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>
|
<textarea class="form-control font-monospace" id="chapterOutputJson" rows="10" readonly>@chapter.OutputTextJson</textarea>
|
||||||
@ -300,4 +314,58 @@ else
|
|||||||
|
|
||||||
return "Scene failed. See parsed JSON for details.";
|
return "Scene failed. See parsed JSON for details.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int? HighestReturnedEndParagraph(StoryIntelligenceSavedChapterResult chapter)
|
||||||
|
=> ReadBoundaryRanges(chapter).Select(range => range.End).DefaultIfEmpty().Max() is var max && max > 0 ? max : null;
|
||||||
|
|
||||||
|
private static string DisplayBoundaryRanges(StoryIntelligenceSavedChapterResult chapter)
|
||||||
|
{
|
||||||
|
var ranges = ReadBoundaryRanges(chapter).ToList();
|
||||||
|
return ranges.Count == 0
|
||||||
|
? "None"
|
||||||
|
: string.Join(", ", ranges.Select(range => $"{range.Start}-{range.End}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ReturnedEndParagraphExceedsSubmitted(StoryIntelligenceSavedChapterResult chapter, StoryIntelligenceSavedRun run)
|
||||||
|
{
|
||||||
|
var highest = HighestReturnedEndParagraph(chapter);
|
||||||
|
var submitted = run.SourceParagraphCount;
|
||||||
|
return highest.HasValue && submitted.HasValue && highest.Value > submitted.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<(int Start, int End)> ReadBoundaryRanges(StoryIntelligenceSavedChapterResult chapter)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(chapter.ParsedJson))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var document = System.Text.Json.JsonDocument.Parse(chapter.ParsedJson);
|
||||||
|
if (!document.RootElement.TryGetProperty("sceneBoundaries", out var boundaries)
|
||||||
|
|| boundaries.ValueKind != System.Text.Json.JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var ranges = new List<(int Start, int End)>();
|
||||||
|
foreach (var boundary in boundaries.EnumerateArray())
|
||||||
|
{
|
||||||
|
var start = boundary.TryGetProperty("startParagraph", out var startElement) && startElement.TryGetInt32(out var startValue)
|
||||||
|
? startValue
|
||||||
|
: 0;
|
||||||
|
var end = boundary.TryGetProperty("endParagraph", out var endElement) && endElement.TryGetInt32(out var endValue)
|
||||||
|
? endValue
|
||||||
|
: 0;
|
||||||
|
ranges.Add((start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
catch (System.Text.Json.JsonException)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user