PlotDirector/PlotLine/Models/StoryIntelligenceModels.cs

224 lines
9.6 KiB
C#

namespace PlotLine.Models;
public static class StoryIntelligenceJobStatuses
{
public const string Pending = "Pending";
public const string Preparing = "Preparing";
public const string ReadingScenes = "ReadingScenes";
public const string AnalysingCharacters = "AnalysingCharacters";
public const string AnalysingRelationships = "AnalysingRelationships";
public const string AnalysingLocations = "AnalysingLocations";
public const string AnalysingAssets = "AnalysingAssets";
public const string AnalysingTimeline = "AnalysingTimeline";
public const string Finalising = "Finalising";
public const string Completed = "Completed";
public const string Cancelled = "Cancelled";
public const string Failed = "Failed";
}
public sealed class StoryIntelligenceJob
{
public Guid JobID { get; init; }
public int ConsentID { get; init; }
public int UserID { get; init; }
public int ProjectID { get; init; }
public int BookID { get; init; }
public string Status { get; init; } = StoryIntelligenceJobStatuses.Pending;
public DateTime CreatedUtc { get; init; }
public DateTime? StartedUtc { get; init; }
public DateTime? CompletedUtc { get; init; }
public DateTime? CancelledUtc { get; init; }
public int ProgressPercent { get; init; }
public string CurrentStage { get; init; } = StoryIntelligenceJobStatuses.Pending;
public string CurrentMessage { get; init; } = "Story Intelligence is queued.";
public string? EstimatedRemaining { get; init; }
public string? ErrorMessage { get; init; }
public DateTime UpdatedUtc { get; init; }
public bool IsActive => Status is StoryIntelligenceJobStatuses.Pending
or StoryIntelligenceJobStatuses.Preparing
or StoryIntelligenceJobStatuses.ReadingScenes
or StoryIntelligenceJobStatuses.AnalysingCharacters
or StoryIntelligenceJobStatuses.AnalysingRelationships
or StoryIntelligenceJobStatuses.AnalysingLocations
or StoryIntelligenceJobStatuses.AnalysingAssets
or StoryIntelligenceJobStatuses.AnalysingTimeline
or StoryIntelligenceJobStatuses.Finalising;
public bool IsCompleted => string.Equals(Status, StoryIntelligenceJobStatuses.Completed, StringComparison.Ordinal);
public bool IsCancelled => string.Equals(Status, StoryIntelligenceJobStatuses.Cancelled, StringComparison.Ordinal);
}
public sealed class StoryIntelligenceJobProgress
{
public Guid JobID { get; init; }
public int UserID { get; init; }
public int ProjectID { get; init; }
public int BookID { get; init; }
public string Status { get; init; } = StoryIntelligenceJobStatuses.Pending;
public string CurrentStage { get; init; } = StoryIntelligenceJobStatuses.Pending;
public string CurrentMessage { get; init; } = "Story Intelligence is queued.";
public int ProgressPercent { get; init; }
public string? EstimatedRemaining { get; init; }
public string ElapsedTime { get; init; } = "0 min";
public string? ErrorMessage { get; init; }
public DateTime UpdatedUtc { get; init; }
public bool IsActive { get; init; }
public bool IsCompleted { get; init; }
}
public sealed class StoryIntelligenceRunProgressEvent
{
public int RunID { get; init; }
public int UserID { get; init; }
public int? ProjectID { get; init; }
public int? BookID { get; init; }
public int? ChapterID { get; init; }
public decimal? ChapterNumber { get; init; }
public string Status { get; init; } = StoryIntelligenceRunStatuses.Pending;
public string CurrentStage { get; init; } = string.Empty;
public string CurrentMessage { get; init; } = string.Empty;
public string EventType { get; init; } = string.Empty;
public int? TotalDetectedScenes { get; init; }
public int? CompletedScenes { get; init; }
public int? FailedScenes { get; init; }
public int? TotalTokens { get; init; }
public long? TotalDurationMs { get; init; }
public int? CurrentSceneNumber { get; init; }
public string ElapsedTime { get; init; } = "0 sec";
public string? EstimatedRemaining { get; init; }
public string? LatestSceneSummary { get; init; }
public IReadOnlyList<string> RecentDiscoveries { get; init; } = [];
public string? ErrorMessage { get; init; }
public DateTime UpdatedUtc { get; init; } = DateTime.UtcNow;
public bool IsActive { get; init; }
public bool IsCompleted { get; init; }
}
public sealed class StoryIntelligenceOptions
{
public string ApiKey { get; init; } = string.Empty;
public string Model { get; init; } = string.Empty;
public string ChapterStructureModel { get; init; } = string.Empty;
public string SceneIntelligenceModel { get; init; } = string.Empty;
public string ImageGenerationModel { get; init; } = string.Empty;
public string ImageGenerationSize { get; init; } = "1024x1024";
public int MaxOutputTokens { get; init; } = 4000;
public int? SceneIntelligenceMaxOutputTokens { get; init; }
public int TimeoutSeconds { get; init; } = 120;
public string EffectiveModel => FirstConfigured(Model);
public string EffectiveChapterStructureModel => FirstConfigured(ChapterStructureModel, Model);
public string EffectiveSceneIntelligenceModel => FirstConfigured(SceneIntelligenceModel, Model);
public static string BuildModelSummary(string? chapterStructureModel, string? sceneIntelligenceModel)
=> $"Chapter={FirstConfigured(chapterStructureModel)}; Scene={FirstConfigured(sceneIntelligenceModel)}";
public static StoryIntelligenceStageModels ParseModelSummary(
string? modelSummary,
string defaultChapterStructureModel,
string defaultSceneIntelligenceModel)
{
var chapterModel = defaultChapterStructureModel;
var sceneModel = defaultSceneIntelligenceModel;
if (!string.IsNullOrWhiteSpace(modelSummary))
{
var segments = modelSummary.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var matchedStage = false;
foreach (var segment in segments)
{
var parts = segment.Split('=', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2 || string.IsNullOrWhiteSpace(parts[1]))
{
continue;
}
if (string.Equals(parts[0], "Chapter", StringComparison.OrdinalIgnoreCase))
{
chapterModel = parts[1];
matchedStage = true;
}
else if (string.Equals(parts[0], "Scene", StringComparison.OrdinalIgnoreCase))
{
sceneModel = parts[1];
matchedStage = true;
}
}
if (!matchedStage && !string.IsNullOrWhiteSpace(modelSummary))
{
chapterModel = modelSummary.Trim();
sceneModel = modelSummary.Trim();
}
}
return new StoryIntelligenceStageModels(
FirstConfigured(chapterModel, defaultChapterStructureModel),
FirstConfigured(sceneModel, defaultSceneIntelligenceModel));
}
private static string FirstConfigured(params string?[] values)
=> values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value))?.Trim() ?? string.Empty;
}
public sealed record StoryIntelligenceStageModels(string ChapterStructureModel, string SceneIntelligenceModel);
public sealed class StoryIntelligencePricingOptions
{
public decimal? InputPerMillionUsd { get; init; }
public decimal? OutputPerMillionUsd { get; init; }
public decimal? UsdToGbpRate { get; init; }
public Dictionary<string, StoryIntelligenceModelPricingOptions> Models { get; init; } = new(StringComparer.OrdinalIgnoreCase);
public StoryIntelligenceModelPricingOptions? GetPricingForModel(string? model)
{
if (!string.IsNullOrWhiteSpace(model)
&& Models.TryGetValue(model.Trim(), out var modelPricing)
&& (modelPricing.InputPerMillionUsd.HasValue || modelPricing.OutputPerMillionUsd.HasValue))
{
return modelPricing;
}
return InputPerMillionUsd.HasValue || OutputPerMillionUsd.HasValue
? new StoryIntelligenceModelPricingOptions
{
InputPerMillionUsd = InputPerMillionUsd,
OutputPerMillionUsd = OutputPerMillionUsd
}
: null;
}
}
public sealed class StoryIntelligenceModelPricingOptions
{
public decimal? InputPerMillionUsd { get; init; }
public decimal? OutputPerMillionUsd { get; init; }
}
public sealed class StoryIntelligenceRun
{
public Guid RunId { get; init; } = Guid.NewGuid();
public int ProjectId { get; init; }
public int BookId { get; init; }
public DateTime StartedUtc { get; init; } = DateTime.UtcNow;
public DateTime? CompletedUtc { get; init; }
public string Status { get; init; } = "Pending";
public string PromptVersion { get; init; } = string.Empty;
public string Model { get; init; } = string.Empty;
public int? InputTokens { get; init; }
public int? OutputTokens { get; init; }
public decimal? EstimatedCost { get; init; }
public bool Cancelled { get; init; }
public string? ErrorMessage { get; init; }
}
public sealed class StoryIntelligenceClientResult
{
public string RawResponseText { get; init; } = string.Empty;
public string Model { get; init; } = string.Empty;
public TimeSpan Duration { get; init; }
public int RetryCount { get; init; }
public int? InputTokens { get; init; }
public int? OutputTokens { get; init; }
public decimal? EstimatedCost { get; init; }
}