151 lines
6.5 KiB
C#
151 lines
6.5 KiB
C#
using PlotLine.Data;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.Services;
|
|
|
|
public interface IStoryIntelligenceExistingChapterQueueService
|
|
{
|
|
Task<StoryIntelligenceExistingChapterQueueViewModel> BuildViewModelAsync(StoryIntelligenceExistingChapterQueueForm form);
|
|
Task<StoryIntelligenceExistingChapterQueueViewModel> QueueAsync(int userId, StoryIntelligenceExistingChapterQueueForm form);
|
|
}
|
|
|
|
public sealed class StoryIntelligenceExistingChapterQueueService(
|
|
IStoryIntelligenceSourceRepository sources,
|
|
IStoryIntelligenceResultRepository runs,
|
|
IStoryIntelligenceClient client,
|
|
IStoryIntelligencePipelineStateService pipelines,
|
|
ILogger<StoryIntelligenceExistingChapterQueueService> logger) : IStoryIntelligenceExistingChapterQueueService
|
|
{
|
|
private const string NoStoredChapterTextMessage = "No stored chapter text is currently available for this chapter. A future Word Companion/document extraction phase is required.";
|
|
|
|
public async Task<StoryIntelligenceExistingChapterQueueViewModel> BuildViewModelAsync(StoryIntelligenceExistingChapterQueueForm form)
|
|
{
|
|
var selected = await GetSelectedChapterAsync(form);
|
|
return await BuildViewModelAsync(form, selected, warningMessage: BuildWarning(selected), errorMessage: null, queuedRunId: null);
|
|
}
|
|
|
|
public async Task<StoryIntelligenceExistingChapterQueueViewModel> QueueAsync(int userId, StoryIntelligenceExistingChapterQueueForm form)
|
|
{
|
|
if (form.ChapterID is not int chapterId)
|
|
{
|
|
return await BuildViewModelAsync(
|
|
form,
|
|
selectedChapter: null,
|
|
warningMessage: null,
|
|
errorMessage: "Choose a chapter before queueing a Story Intelligence run.",
|
|
queuedRunId: null);
|
|
}
|
|
|
|
var selected = await sources.GetChapterSourceAsync(chapterId);
|
|
if (selected is null)
|
|
{
|
|
return await BuildViewModelAsync(
|
|
form,
|
|
selectedChapter: null,
|
|
warningMessage: null,
|
|
errorMessage: "The selected chapter could not be found.",
|
|
queuedRunId: null);
|
|
}
|
|
|
|
if (!selected.HasSourceText)
|
|
{
|
|
return await BuildViewModelAsync(
|
|
NormalizeForm(selected),
|
|
selected,
|
|
NoStoredChapterTextMessage,
|
|
errorMessage: null,
|
|
queuedRunId: null);
|
|
}
|
|
|
|
var sourceText = selected.SourceText!.Trim();
|
|
var clientStatus = client.GetConfigurationStatus();
|
|
var runId = await runs.QueueAdminTextAsync(new StoryIntelligenceRunQueueRequest
|
|
{
|
|
UserID = userId,
|
|
ProjectID = selected.ProjectID,
|
|
BookID = selected.BookID,
|
|
ChapterID = selected.ChapterID,
|
|
ChapterNumber = selected.ChapterNumber,
|
|
SourceType = "ExistingChapter",
|
|
SourceLabel = BuildSourceLabel(selected),
|
|
ChapterText = sourceText,
|
|
SourceWordCount = selected.SourceWordCount ?? CountWords(sourceText),
|
|
SourceCharacterCount = selected.SourceCharacterCount ?? sourceText.Length,
|
|
SourceParagraphCount = StoryIntelligenceParagraphs.Split(sourceText).Count,
|
|
SourceChapterCount = 1,
|
|
Model = StoryIntelligenceOptions.BuildModelSummary(
|
|
clientStatus.ChapterStructureModel,
|
|
clientStatus.SceneIntelligenceModel),
|
|
PromptVersionsSummary = "Chapter=Chapter-Structure-Prompt-V2; Scene=Scene-Prompt-V2"
|
|
});
|
|
await pipelines.RecordChapterAnalysisQueuedAsync(selected.ProjectID, selected.BookID, runId);
|
|
|
|
logger.LogInformation(
|
|
"Queued Story Intelligence run {StoryIntelligenceRunID} from existing chapter {ChapterID}. ProjectID={ProjectID} BookID={BookID} SourceWordCount={SourceWordCount} SourceCharacterCount={SourceCharacterCount}",
|
|
runId,
|
|
selected.ChapterID,
|
|
selected.ProjectID,
|
|
selected.BookID,
|
|
selected.SourceWordCount,
|
|
selected.SourceCharacterCount);
|
|
|
|
return await BuildViewModelAsync(NormalizeForm(selected), selected, warningMessage: null, errorMessage: null, queuedRunId: runId);
|
|
}
|
|
|
|
private async Task<StoryIntelligenceExistingChapterQueueViewModel> BuildViewModelAsync(
|
|
StoryIntelligenceExistingChapterQueueForm form,
|
|
StoryIntelligenceExistingChapterSource? selectedChapter,
|
|
string? warningMessage,
|
|
string? errorMessage,
|
|
int? queuedRunId)
|
|
{
|
|
var normalizedForm = selectedChapter is not null ? NormalizeForm(selectedChapter) : form;
|
|
var projects = await sources.ListProjectsAsync();
|
|
var books = normalizedForm.ProjectID is int projectId
|
|
? await sources.ListBooksAsync(projectId)
|
|
: [];
|
|
var chapters = normalizedForm.BookID is int bookId
|
|
? await sources.ListChaptersAsync(bookId)
|
|
: [];
|
|
|
|
return new StoryIntelligenceExistingChapterQueueViewModel
|
|
{
|
|
Form = normalizedForm,
|
|
Projects = projects,
|
|
Books = books,
|
|
Chapters = chapters,
|
|
SelectedChapter = selectedChapter,
|
|
WarningMessage = warningMessage,
|
|
ErrorMessage = errorMessage,
|
|
QueuedRunID = queuedRunId
|
|
};
|
|
}
|
|
|
|
private async Task<StoryIntelligenceExistingChapterSource?> GetSelectedChapterAsync(StoryIntelligenceExistingChapterQueueForm form)
|
|
=> form.ChapterID is int chapterId ? await sources.GetChapterSourceAsync(chapterId) : null;
|
|
|
|
private static string? BuildWarning(StoryIntelligenceExistingChapterSource? selected)
|
|
=> selected is not null && !selected.HasSourceText ? NoStoredChapterTextMessage : null;
|
|
|
|
private static StoryIntelligenceExistingChapterQueueForm NormalizeForm(StoryIntelligenceExistingChapterSource selected)
|
|
=> new()
|
|
{
|
|
ProjectID = selected.ProjectID,
|
|
BookID = selected.BookID,
|
|
ChapterID = selected.ChapterID
|
|
};
|
|
|
|
private static string BuildSourceLabel(StoryIntelligenceExistingChapterSource selected)
|
|
{
|
|
var book = string.IsNullOrWhiteSpace(selected.BookSubtitle)
|
|
? selected.BookTitle
|
|
: $"{selected.BookTitle}: {selected.BookSubtitle}";
|
|
return $"{selected.ProjectName} / {book} / Chapter {selected.ChapterNumber}: {selected.ChapterTitle}";
|
|
}
|
|
|
|
private static int? CountWords(string? value)
|
|
=> string.IsNullOrWhiteSpace(value)
|
|
? null
|
|
: value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
|
|
}
|