Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SDD ledger β€” plan: docs/superpowers/plans/2026-09-07-studio-presets-test-flight.md

## Tasks
- [x] Task 1: Core Models & StudioPresetService with Unit Tests (commit 1ac7764)
- [x] Task 2: Hardware Fit Pre-Flight Estimation in CanIRunItService (commit 70d3636)
- [x] Task 3: Reusable Avalonia UI Controls (commit 2fe72f9)
- [x] Task 4: Integrate Studio Presets, Hardware Fit, & Stage Tracking into Studio ViewModels and UI (commit 4cb3bc3)
- [x] Task 5: Centralized Presets Manager in Settings View (commit afa0d2c)
- [x] Task 6: Verification, Version Bump, and Branch/PR Preparation (577 passed tests, lint clean, typecheck clean)
2 changes: 1 addition & 1 deletion Endpoints/HealthEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static void MapHealthEndpoints(this WebApplication app)
StableDiffusion = forgeHealthy ? "Online" : "Offline",
ComfyUI = comfyHealthy ? "Online" : "Offline",
PreferredImageEngine = settings.PreferredImageEngine,
Version = typeof(Program).Assembly.GetName().Version?.ToString(3) ?? "3.12.1"
Version = "3.13.0"
});
});
}
Expand Down
20 changes: 20 additions & 0 deletions LocalLLMServerManager.Shared/Interfaces/IStudioPresetService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace LocalLLMServerManager.Shared.Interfaces;

using System.Collections.Generic;
using LocalLLMServerManager.Shared.Models;

/// <summary>
/// Service interface for managing built-in and user-defined studio presets.
/// </summary>
public interface IStudioPresetService
{
IReadOnlyList<StudioPreset> GetPresets(StudioModality modality);
IReadOnlyList<StudioPreset> GetAllPresets();
StudioPreset? GetPresetById(string id);
void SavePreset(StudioPreset preset);
bool DeletePreset(string id);
StudioPreset? DuplicatePreset(string id);
string ExportJson();
bool ImportJson(string json);
void ResetToDefaults();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>3.12.1</Version>
<AssemblyVersion>3.12.1.0</AssemblyVersion>
<FileVersion>3.12.1.0</FileVersion>
<Version>3.13.0</Version>
<AssemblyVersion>3.13.0.0</AssemblyVersion>
<FileVersion>3.13.0.0</FileVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Expand Down
7 changes: 6 additions & 1 deletion LocalLLMServerManager.Shared/Models/AppSettings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace LocalLLMServerManager;

using System.Collections.Generic;
using LocalLLMServerManager.Shared.Models;

/// <summary>
/// Application settings persisted to settings.json next to the executable.
/// </summary>
Expand All @@ -22,6 +25,8 @@ public record AppSettings(
string AudioEngineUrl = "http://127.0.0.1:8880",
string PreferredAudioVoice = "af_heart",
string VideoModelsPath = "",
string VideoOutputPath = ""
string VideoOutputPath = "",
List<StudioPreset>? CustomPresets = null
);


11 changes: 11 additions & 0 deletions LocalLLMServerManager.Shared/Models/HardwareFitModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,14 @@ public record TelemetryInfo(
double AvailableRamMb
);

/// <summary>
/// Pre-flight hardware compatibility assessment for Studio generation jobs.
/// </summary>
public record StudioHardwareFit(
QuickFitBadge FitBadge,
double EstimatedVramMb,
string StatusText,
string RecommendedPresetName,
bool RequiresLlmUnload
);

54 changes: 54 additions & 0 deletions LocalLLMServerManager.Shared/Models/StudioPresetModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace LocalLLMServerManager.Shared.Models;

using System;

/// <summary>
/// Studio modality types supported for generation presets.
/// </summary>
public enum StudioModality
{
Image,
Video,
Audio
}

/// <summary>
/// A preset configuration for Image, Video, or Audio/TTS generation.
/// </summary>
public record StudioPreset
{
public string Id { get; init; } = Guid.NewGuid().ToString();
public string Name { get; init; } = "";
public string Description { get; init; } = "";
public StudioModality Modality { get; init; } = StudioModality.Image;
public string WorkflowOrEngine { get; init; } = "";
public int Width { get; init; } = 832;
public int Height { get; init; } = 480;
public int FrameCount { get; init; } = 48;
public int Fps { get; init; } = 16;
public int DurationSeconds { get; init; } = 3;
public string VoiceProfile { get; init; } = "";
public string SamplePrompt { get; init; } = "";
public string NegativePrompt { get; init; } = "";
public bool IsBuiltIn { get; init; } = false;

public bool IsCustom => !IsBuiltIn;

public string SummaryText => Modality switch
{
StudioModality.Video => $"{Width}x{Height} β€’ {FrameCount} frames β€’ {Fps} fps",
StudioModality.Image => $"{Width}x{Height}",
StudioModality.Audio => !string.IsNullOrWhiteSpace(VoiceProfile) ? $"{VoiceProfile} β€’ {DurationSeconds}s" : $"{DurationSeconds}s audio",
_ => $"{Width}x{Height}"
};

public string BadgeText => IsBuiltIn ? "πŸ”’ Built-in" : "✨ Custom";

public string ModalityText => Modality switch
{
StudioModality.Video => "🎬 Video",
StudioModality.Image => "🎨 Image",
StudioModality.Audio => "🎡 Audio",
_ => Modality.ToString()
};
}
90 changes: 90 additions & 0 deletions LocalLLMServerManager.Shared/Services/CanIRunItService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@
};

return new DiffusionFitResult(
ModelName: request.ModelName,

Check warning on line 302 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'ModelName' in 'DiffusionFitResult.DiffusionFitResult(string ModelName, long BaseModelMb, long EncodersMb, long VaeMb, long LatentBufferMb, long TotalVramMb, long TotalRamMb, FitVerdict FitVerdict, double EstimatedSecondsPerImage, string RecommendationMessage)'.

Check warning on line 302 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'ModelName' in 'DiffusionFitResult.DiffusionFitResult(string ModelName, long BaseModelMb, long EncodersMb, long VaeMb, long LatentBufferMb, long TotalVramMb, long TotalRamMb, FitVerdict FitVerdict, double EstimatedSecondsPerImage, string RecommendationMessage)'.
BaseModelMb: baseMb,
EncodersMb: encMb,
VaeMb: vaeMb,
Expand Down Expand Up @@ -400,7 +400,7 @@
};

return new VideoFitResult(
ModelName: request.ModelName,

Check warning on line 403 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'ModelName' in 'VideoFitResult.VideoFitResult(string ModelName, long DiTModelMb, long FrameContextMb, long VaeDecodeMb, long TotalVramMb, long TotalRamMb, FitVerdict FitVerdict, double EstimatedSecondsPerFrame, string RecommendationMessage)'.

Check warning on line 403 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'ModelName' in 'VideoFitResult.VideoFitResult(string ModelName, long DiTModelMb, long FrameContextMb, long VaeDecodeMb, long TotalVramMb, long TotalRamMb, FitVerdict FitVerdict, double EstimatedSecondsPerFrame, string RecommendationMessage)'.
DiTModelMb: ditMb,
FrameContextMb: frameMb,
VaeDecodeMb: vaeMb,
Expand Down Expand Up @@ -466,7 +466,7 @@
verdict = FitVerdict.OutOfMemory;

return new AudioFitResult(
EngineName: engineName,

Check warning on line 469 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'EngineName' in 'AudioFitResult.AudioFitResult(string EngineName, long VramRequiredMb, long RamRequiredMb, FitVerdict FitVerdict, double EstimatedRealtimeFactor, string RecommendationMessage)'.

Check warning on line 469 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'EngineName' in 'AudioFitResult.AudioFitResult(string EngineName, long VramRequiredMb, long RamRequiredMb, FitVerdict FitVerdict, double EstimatedRealtimeFactor, string RecommendationMessage)'.
VramRequiredMb: vramReq,
RamRequiredMb: ramReq,
FitVerdict: verdict,
Expand Down Expand Up @@ -515,7 +515,7 @@
verdict = FitVerdict.OutOfMemory;

return new ThreeDFitResult(
ModelName: modelName,

Check warning on line 518 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'ModelName' in 'ThreeDFitResult.ThreeDFitResult(string ModelName, long VramRequiredMb, long RamRequiredMb, FitVerdict FitVerdict, double EstimatedSecondsPerMesh, string RecommendationMessage)'.

Check warning on line 518 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'ModelName' in 'ThreeDFitResult.ThreeDFitResult(string ModelName, long VramRequiredMb, long RamRequiredMb, FitVerdict FitVerdict, double EstimatedSecondsPerMesh, string RecommendationMessage)'.
VramRequiredMb: vramReq,
RamRequiredMb: ramReq,
FitVerdict: verdict,
Expand All @@ -535,25 +535,25 @@

if (mod.Contains("audio") || mod.Contains("speech") || mod.Contains("tts") || name.Contains("kokoro") || name.Contains("whisper"))
{
var audioResult = EvaluateAudioFit(modelName, vramMb, ramMb);

Check warning on line 538 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'engineName' in 'AudioFitResult CanIRunItService.EvaluateAudioFit(string engineName, long vramMb, long ramMb)'.

Check warning on line 538 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'engineName' in 'AudioFitResult CanIRunItService.EvaluateAudioFit(string engineName, long vramMb, long ramMb)'.
verdict = audioResult.FitVerdict;
tooltipDetail = $"{audioResult.VramRequiredMb} MB VRAM needed. {audioResult.RecommendationMessage}";
}
else if (mod.Contains("3d") || name.Contains("trellis") || name.Contains("hunyuan3d"))
{
var threeDResult = Evaluate3DFit(modelName, vramMb, ramMb);

Check warning on line 544 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'modelName' in 'ThreeDFitResult CanIRunItService.Evaluate3DFit(string modelName, long vramMb, long ramMb)'.

Check warning on line 544 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'modelName' in 'ThreeDFitResult CanIRunItService.Evaluate3DFit(string modelName, long vramMb, long ramMb)'.
verdict = threeDResult.FitVerdict;
tooltipDetail = $"{threeDResult.VramRequiredMb} MB VRAM needed. {threeDResult.RecommendationMessage}";
}
else if (mod.Contains("video") || name.Contains("wan") || name.Contains("ltx"))
{
var vidResult = EvaluateVideoFit(new VideoFitRequest(modelName, "FP8", 49, 720, vramMb, ramMb));

Check warning on line 550 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'ModelName' in 'VideoFitRequest.VideoFitRequest(string ModelName = "Wan 2.2 14B", string Quantization = "FP8", int FrameCount = 49, int Resolution = 720, long AvailableVramMb = 16384, long AvailableRamMb = 32768)'.

Check warning on line 550 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'ModelName' in 'VideoFitRequest.VideoFitRequest(string ModelName = "Wan 2.2 14B", string Quantization = "FP8", int FrameCount = 49, int Resolution = 720, long AvailableVramMb = 16384, long AvailableRamMb = 32768)'.
verdict = vidResult.FitVerdict;
tooltipDetail = $"{vidResult.TotalVramMb + vidResult.TotalRamMb} MB needed. {vidResult.RecommendationMessage}";
}
else if (mod.Contains("image") || mod.Contains("diffusion") || name.Contains("flux") || name.Contains("sdxl") || name.Contains("sd 3"))
{
var diffResult = EvaluateDiffusionFit(new DiffusionFitRequest(modelName, "FP8", 1024, vramMb, ramMb));

Check warning on line 556 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'ModelName' in 'DiffusionFitRequest.DiffusionFitRequest(string ModelName = "Flux.1 Dev", string Quantization = "FP8", int Resolution = 1024, long AvailableVramMb = 16384, long AvailableRamMb = 32768)'.

Check warning on line 556 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'ModelName' in 'DiffusionFitRequest.DiffusionFitRequest(string ModelName = "Flux.1 Dev", string Quantization = "FP8", int Resolution = 1024, long AvailableVramMb = 16384, long AvailableRamMb = 32768)'.
verdict = diffResult.FitVerdict;
tooltipDetail = $"{diffResult.TotalVramMb + diffResult.TotalRamMb} MB needed. {diffResult.RecommendationMessage}";
}
Expand All @@ -576,7 +576,7 @@
}
else
{
double paramBillions = ExtractParamBillions(modelName);

Check warning on line 579 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

Possible null reference argument for parameter 'modelName' in 'double CanIRunItService.ExtractParamBillions(string modelName)'.

Check warning on line 579 in LocalLLMServerManager.Shared/Services/CanIRunItService.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

Possible null reference argument for parameter 'modelName' in 'double CanIRunItService.ExtractParamBillions(string modelName)'.
string quant = ExtractQuantization(modelName);

var llmResult = EvaluateLlmFit(new LlmFitRequest(
Expand Down Expand Up @@ -609,6 +609,96 @@
);
}

/// <inheritdoc />
public StudioHardwareFit EstimateStudioHardwareFit(StudioModality modality, int width, int height, int frameCount, string workflow, double freeVramMb, double totalVramMb)
{
string wf = (workflow ?? "").Trim().ToLowerInvariant();
double estimatedVramMb;

switch (modality)
{
case StudioModality.Audio:
if (wf.Contains("stable") || wf.Contains("music") || wf.Contains("song") || wf.Contains("yue") || wf.Contains("audiocraft"))
{
estimatedVramMb = 2500.0;
}
else
{
// Kokoro or default TTS / speech
estimatedVramMb = 1500.0;
}
break;

case StudioModality.Image:
int imgW = width > 0 ? width : 1024;
int imgH = height > 0 ? height : 1024;
double imgPixels = (double)imgW * imgH;
double baseImagePixels = 1024.0 * 1024.0;
estimatedVramMb = Math.Round(4000.0 * (imgPixels / baseImagePixels));
break;

case StudioModality.Video:
default:
int vidW = width > 0 ? width : 832;
int vidH = height > 0 ? height : 480;
int vidFrames = frameCount > 0 ? frameCount : 48;
double vidPixels = (double)vidW * vidH;
// Baseline ~6000 MB for 480p (832x480, 48 frames), ~10000 MB for 720p (1280x720, 48 frames)
double baseDitMb = 3000.0;
double frameContextScaling = 3000.0 * (vidPixels / (832.0 * 480.0)) * (vidFrames / 48.0);
estimatedVramMb = Math.Round(baseDitMb + frameContextScaling);
break;
}

QuickFitBadge fitBadge;
string statusText;
string recommendedPreset = "";
bool requiresLlmUnload;

if (estimatedVramMb <= freeVramMb)
{
fitBadge = new QuickFitBadge(
BadgeText: "🟒 Ready",
BadgeColorHex: "#10B981",
Tooltip: $"Requires ~{estimatedVramMb:N0} MB VRAM. Fits comfortably in free GPU memory ({freeVramMb:N0} MB free).",
FitVerdict: FitVerdict.FullVram
);
statusText = "Ready";
requiresLlmUnload = false;
}
else if (estimatedVramMb <= totalVramMb)
{
fitBadge = new QuickFitBadge(
BadgeText: "🟑 Tight Fit",
BadgeColorHex: "#F59E0B",
Tooltip: $"Requires ~{estimatedVramMb:N0} MB VRAM. Exceeds free VRAM ({freeVramMb:N0} MB) but fits in total VRAM ({totalVramMb:N0} MB). Active LLM will be unloaded.",
FitVerdict: FitVerdict.PartialOffload
);
statusText = "Tight Fit";
requiresLlmUnload = true;
}
else
{
fitBadge = new QuickFitBadge(
BadgeText: "πŸ”΄ Exceeds GPU Limit",
BadgeColorHex: "#EF4444",
Tooltip: $"Requires ~{estimatedVramMb:N0} MB VRAM which exceeds total GPU capacity ({totalVramMb:N0} MB). Consider switching to a lower resolution preset.",
FitVerdict: FitVerdict.OutOfMemory
);
statusText = "Exceeds GPU Limit";
recommendedPreset = "Quick 480p Preview";
requiresLlmUnload = true;
}

return new StudioHardwareFit(
FitBadge: fitBadge,
EstimatedVramMb: estimatedVramMb,
StatusText: statusText,
RecommendedPresetName: recommendedPreset,
RequiresLlmUnload: requiresLlmUnload
);
}

private static double ExtractParamBillions(string modelName)
{
if (string.IsNullOrWhiteSpace(modelName))
Expand Down
7 changes: 7 additions & 0 deletions LocalLLMServerManager.Shared/Services/ICanIRunItService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,11 @@ public interface ICanIRunItService
/// Generates a lightweight compatibility badge suitable for search/library cards.
/// </summary>
QuickFitBadge EvaluateQuickFit(string modelName, long? fileSizeBytes, string modality, long vramMb, long ramMb);

/// <summary>
/// Evaluates pre-flight hardware fit for studio generation workloads (Image, Video, Audio)
/// based on modality, resolution, frame counts, workflow, and current VRAM telemetry.
/// </summary>
StudioHardwareFit EstimateStudioHardwareFit(StudioModality modality, int width, int height, int frameCount, string workflow, double freeVramMb, double totalVramMb);
}

Loading
Loading