using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics.Contracts; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Avalonia.Data; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Models; namespace StabilityMatrix.Avalonia.Models.Inference; public partial class FileNameFormatProvider { public GenerationParameters? GenerationParameters { get; init; } public InferenceProjectType? ProjectType { get; init; } public string? ProjectName { get; init; } private Dictionary>? _substitutions; public Dictionary> Substitutions => _substitutions ??= new Dictionary> { { "seed", () => GenerationParameters?.Seed.ToString() }, { "prompt", () => GenerationParameters?.PositivePrompt }, { "negative_prompt", () => GenerationParameters?.NegativePrompt }, { "model_name", () => Path.GetFileNameWithoutExtension(GenerationParameters?.ModelName) }, { "model_hash", () => GenerationParameters?.ModelHash }, { "width", () => GenerationParameters?.Width.ToString() }, { "height", () => GenerationParameters?.Height.ToString() }, { "project_type", () => ProjectType?.GetStringValue() }, { "project_name", () => ProjectName }, { "date", () => DateTime.Now.ToString("yyyy-MM-dd") }, { "time", () => DateTime.Now.ToString("HH-mm-ss") } }; /// /// Validate a format string /// /// Format string /// Thrown if the format string contains an unknown variable [Pure] public ValidationResult Validate(string format) { var regex = BracketRegex(); var matches = regex.Matches(format); var variables = matches.Select(m => m.Groups[1].Value); foreach (var variableText in variables) { try { var (variable, _) = ExtractVariableAndSlice(variableText); if (!Substitutions.ContainsKey(variable)) { return new ValidationResult($"Unknown variable '{variable}'"); } } catch (Exception e) { return new ValidationResult($"Invalid variable '{variableText}': {e.Message}"); } } return ValidationResult.Success!; } public IEnumerable GetParts(string template) { var regex = BracketRegex(); var matches = regex.Matches(template); var parts = new List(); // Loop through all parts of the string, including matches and non-matches var currentIndex = 0; foreach (var result in matches.Cast()) { // If the match is not at the start of the string, add a constant part if (result.Index != currentIndex) { var constant = template[currentIndex..result.Index]; parts.Add(constant); currentIndex += constant.Length; } // Now we're at start of the current match, add the variable part var (variable, slice) = ExtractVariableAndSlice(result.Groups[1].Value); var substitution = Substitutions[variable]; // Slice string if necessary if (slice is not null) { parts.Add( (FileNameFormatPart)( () => { var value = substitution(); if (value is null) return null; if (slice.End is null) { value = value[(slice.Start ?? 0)..]; } else { var length = Math.Min(value.Length, slice.End.Value) - (slice.Start ?? 0); value = value.Substring(slice.Start ?? 0, length); } return value; } ) ); } else { parts.Add(substitution); } currentIndex += result.Length; } // Add remaining as constant if (currentIndex != template.Length) { var constant = template[currentIndex..]; parts.Add(constant); } return parts; } /// /// Return a sample provider for UI preview /// public static FileNameFormatProvider GetSample() { return new FileNameFormatProvider { GenerationParameters = GenerationParameters.GetSample(), ProjectType = InferenceProjectType.TextToImage, ProjectName = "Sample Project" }; } /// /// Extract variable and index from a combined string /// private static (string Variable, Slice? Slice) ExtractVariableAndSlice(string combined) { if (IndexRegex().Matches(combined).FirstOrDefault() is not { Success: true } match) { return (combined, null); } // Variable is everything before the match var variable = combined[..match.Groups[0].Index]; var start = match.Groups["start"].Value; var end = match.Groups["end"].Value; var step = match.Groups["step"].Value; var slice = new Slice( string.IsNullOrEmpty(start) ? null : int.Parse(start), string.IsNullOrEmpty(end) ? null : int.Parse(end), string.IsNullOrEmpty(step) ? null : int.Parse(step) ); return (variable, slice); } /// /// Regex for matching contents within a curly brace. /// [GeneratedRegex(@"\{([a-z_:\d\[\]]+)\}")] private static partial Regex BracketRegex(); /// /// Regex for matching a Python-like array index. /// [GeneratedRegex(@"\[(?:(?-?\d+)?)\:(?:(?-?\d+)?)?(?:\:(?-?\d+))?\]")] private static partial Regex IndexRegex(); private record Slice(int? Start, int? End, int? Step); }