Ionite
2 years ago
committed by
GitHub
15 changed files with 733 additions and 34 deletions
@ -0,0 +1,20 @@ |
|||||||
|
name: Remove old artifacts |
||||||
|
|
||||||
|
on: |
||||||
|
workflow_dispatch: |
||||||
|
schedule: |
||||||
|
# Every day at 1am |
||||||
|
- cron: '0 1 * * *' |
||||||
|
|
||||||
|
jobs: |
||||||
|
remove-old-artifacts: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
timeout-minutes: 10 |
||||||
|
|
||||||
|
steps: |
||||||
|
- name: Remove old artifacts |
||||||
|
uses: c-hive/gha-remove-artifacts@v1 |
||||||
|
with: |
||||||
|
age: '1 day' |
||||||
|
skip-tags: true |
||||||
|
skip-recent: 1 |
@ -0,0 +1,18 @@ |
|||||||
|
using System.Threading.Tasks; |
||||||
|
using Refit; |
||||||
|
using StabilityMatrix.Models.Api; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Api; |
||||||
|
|
||||||
|
[Headers("User-Agent: StabilityMatrix")] |
||||||
|
public interface IA3WebApi |
||||||
|
{ |
||||||
|
[Get("/internal/ping")] |
||||||
|
Task<string> GetPing(); |
||||||
|
|
||||||
|
[Post("/sdapi/v1/txt2img")] |
||||||
|
Task<ImageResponse> TextToImage([Body] TextToImageRequest request); |
||||||
|
|
||||||
|
[Get("/sdapi/v1/progress")] |
||||||
|
Task<ProgressResponse> GetProgress([Body] ProgressRequest request); |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
using System; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Windows.Threading; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Helper; |
||||||
|
|
||||||
|
public class AsyncDispatcherTimer : DispatcherTimer |
||||||
|
{ |
||||||
|
public AsyncDispatcherTimer() |
||||||
|
{ |
||||||
|
Tick += AsyncDispatcherTimer_Tick; |
||||||
|
} |
||||||
|
|
||||||
|
private async void AsyncDispatcherTimer_Tick(object? sender, EventArgs e) |
||||||
|
{ |
||||||
|
if (TickTask == null) |
||||||
|
{ |
||||||
|
// no task to run |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (IsRunning && !IsReentrant) |
||||||
|
{ |
||||||
|
// previous task hasn't completed |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try |
||||||
|
{ |
||||||
|
IsRunning = true; |
||||||
|
await TickTask.Invoke(); |
||||||
|
} |
||||||
|
catch (Exception) |
||||||
|
{ |
||||||
|
Debug.WriteLine("Task Failed"); |
||||||
|
throw; |
||||||
|
} |
||||||
|
finally |
||||||
|
{ |
||||||
|
// allow it to run again |
||||||
|
IsRunning = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsReentrant { get; set; } |
||||||
|
public bool IsRunning { get; private set; } |
||||||
|
|
||||||
|
public Func<Task>? TickTask { get; set; } |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Text.Json.Serialization; |
||||||
|
using StabilityMatrix.ViewModels; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Models.Api; |
||||||
|
|
||||||
|
public class ImageResponse |
||||||
|
{ |
||||||
|
[JsonPropertyName("images")] |
||||||
|
public string[] Images { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("info")] |
||||||
|
public string? Info { get; set; } |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Models.Api; |
||||||
|
|
||||||
|
public class ProgressRequest |
||||||
|
{ |
||||||
|
[JsonPropertyName("skip_current_image")] |
||||||
|
public bool? SkipCurrentImage { get; set; } |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Models.Api; |
||||||
|
|
||||||
|
public class ProgressResponse |
||||||
|
{ |
||||||
|
// Range from 0 to 1 |
||||||
|
[JsonPropertyName("progress")] |
||||||
|
public float Progress { get; set; } |
||||||
|
|
||||||
|
// ETA in seconds |
||||||
|
[JsonPropertyName("eta_relative")] |
||||||
|
public float EtaRelative { get; set; } |
||||||
|
|
||||||
|
// state: dict |
||||||
|
|
||||||
|
// The current image in base64 format. opts.show_progress_every_n_steps is required for this to work |
||||||
|
[JsonPropertyName("current_image")] |
||||||
|
public string? CurrentImage { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("textinfo")] |
||||||
|
public string? TextInfo { get; set; } |
||||||
|
|
||||||
|
} |
@ -0,0 +1,134 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Text.Json.Serialization; |
||||||
|
|
||||||
|
namespace StabilityMatrix.Models.Api; |
||||||
|
|
||||||
|
|
||||||
|
public class TextToImageRequest |
||||||
|
{ |
||||||
|
[JsonPropertyName("enable_hr")] |
||||||
|
public bool? EnableHr { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("denoising_strength")] |
||||||
|
public int? DenoisingStrength { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("firstphase_width")] |
||||||
|
public int? FirstPhaseWidth { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("firstphase_height")] |
||||||
|
public int? FirstPhaseHeight { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("hr_scale")] |
||||||
|
public int? HrScale { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("hr_upscaler")] |
||||||
|
public string? HrUpscaler { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("hr_second_pass_steps")] |
||||||
|
public int? HrSecondPassSteps { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("hr_resize_x")] |
||||||
|
public int? HrResizeX { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("hr_resize_y")] |
||||||
|
public int? HrResizeY { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("prompt")] |
||||||
|
public string Prompt { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("styles")] |
||||||
|
public string?[] Styles { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("seed")] |
||||||
|
public int? Seed { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("subseed")] |
||||||
|
public int? Subseed { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("subseed_strength")] |
||||||
|
public int? SubseedStrength { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("seed_resize_from_h")] |
||||||
|
public int? SeedResizeFromH { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("seed_resize_from_w")] |
||||||
|
public int? SeedResizeFromW { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("sampler_name")] |
||||||
|
public string? SamplerName { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("batch_size")] |
||||||
|
public int? BatchSize { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("n_iter")] |
||||||
|
public int? NIter { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("steps")] |
||||||
|
public int? Steps { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("cfg_scale")] |
||||||
|
public int? CfgScale { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("width")] |
||||||
|
public int? Width { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("height")] |
||||||
|
public int? Height { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("restore_faces")] |
||||||
|
public bool? RestoreFaces { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("tiling")] |
||||||
|
public bool? Tiling { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("do_not_save_samples")] |
||||||
|
public bool? DoNotSaveSamples { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("do_not_save_grid")] |
||||||
|
public bool? DoNotSaveGrid { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("negative_prompt")] |
||||||
|
public string? NegativePrompt { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("eta")] |
||||||
|
public int? Eta { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("s_min_uncond")] |
||||||
|
public int? SMinUncond { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("s_churn")] |
||||||
|
public int? SChurn { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("s_tmax")] |
||||||
|
public int? STmax { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("s_tmin")] |
||||||
|
public int? STmin { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("s_noise")] |
||||||
|
public int? SNoise { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("override_settings")] |
||||||
|
public Dictionary<string, string>? OverrideSettings { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("override_settings_restore_afterwards")] |
||||||
|
public bool? OverrideSettingsRestoreAfterwards { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("script_args")] |
||||||
|
public string[]? ScriptArgs { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("sampler_index")] |
||||||
|
public string? SamplerIndex { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("script_name")] |
||||||
|
public string? ScriptName { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("send_images")] |
||||||
|
public bool? SendImages { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("save_images")] |
||||||
|
public bool? SaveImages { get; set; } |
||||||
|
|
||||||
|
[JsonPropertyName("alwayson_scripts")] |
||||||
|
public Dictionary<string, string>? AlwaysOnScripts { get; set; } |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
<Page |
||||||
|
Background="{DynamicResource ApplicationBackgroundBrush}" |
||||||
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}" |
||||||
|
Title="TextToImagePage" |
||||||
|
d:DataContext="{d:DesignInstance Type=viewModels:TextToImageViewModel, |
||||||
|
IsDesignTimeCreatable=True}" |
||||||
|
d:DesignHeight="700" |
||||||
|
d:DesignWidth="1100" |
||||||
|
mc:Ignorable="d" |
||||||
|
x:Class="StabilityMatrix.TextToImagePage" |
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||||
|
xmlns:local="clr-namespace:StabilityMatrix" |
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||||
|
xmlns:models="clr-namespace:StabilityMatrix.Models" |
||||||
|
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" |
||||||
|
xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels" |
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||||
|
|
||||||
|
<Grid Margin="20"> |
||||||
|
<Grid.RowDefinitions> |
||||||
|
<RowDefinition Height="*" /> |
||||||
|
<RowDefinition Height="Auto" /> |
||||||
|
</Grid.RowDefinitions> |
||||||
|
|
||||||
|
<!-- Controls --> |
||||||
|
<Grid> |
||||||
|
<Grid.ColumnDefinitions> |
||||||
|
<ColumnDefinition Width="*" /> |
||||||
|
<ColumnDefinition Width="*" /> |
||||||
|
</Grid.ColumnDefinitions> |
||||||
|
|
||||||
|
<ScrollViewer |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
Margin="8,8,8,16" |
||||||
|
VerticalAlignment="Stretch"> |
||||||
|
<StackPanel> |
||||||
|
<ui:Card HorizontalAlignment="Stretch" Padding="8"> |
||||||
|
<StackPanel Margin="8"> |
||||||
|
<Label |
||||||
|
Content="Prompt" |
||||||
|
FontSize="14" |
||||||
|
Margin="2" /> |
||||||
|
<ui:AutoSuggestBox |
||||||
|
Margin="8" |
||||||
|
Name="PositivePromptBox" |
||||||
|
PlaceholderText="Prompt" |
||||||
|
QuerySubmitted="PositivePromptBox_OnQuerySubmitted" |
||||||
|
SuggestionChosen="PositivePromptBox_OnSuggestionChosen" |
||||||
|
TextChanged="PositivePromptBox_OnTextChanged" /> |
||||||
|
<Label |
||||||
|
Content="Negative Prompt" |
||||||
|
FontSize="14" |
||||||
|
Margin="2" /> |
||||||
|
<ui:AutoSuggestBox |
||||||
|
Margin="8" |
||||||
|
Name="NegativePromptBox" |
||||||
|
PlaceholderText="Negative Prompt" |
||||||
|
QuerySubmitted="NegativePromptBox_OnQuerySubmitted" |
||||||
|
SuggestionChosen="NegativePromptBox_OnSuggestionChosen" |
||||||
|
TextChanged="NegativePromptBox_OnTextChanged" /> |
||||||
|
|
||||||
|
<Grid Margin="8"> |
||||||
|
<Grid.ColumnDefinitions> |
||||||
|
<ColumnDefinition Width="*" /> |
||||||
|
<ColumnDefinition Width="Auto" /> |
||||||
|
</Grid.ColumnDefinitions> |
||||||
|
<Label |
||||||
|
Content="Steps" |
||||||
|
FontSize="14" |
||||||
|
Margin="2" |
||||||
|
VerticalAlignment="Center" /> |
||||||
|
<TextBox |
||||||
|
FontSize="14" |
||||||
|
Grid.Column="1" |
||||||
|
Margin="8" |
||||||
|
Text="{Binding GenerationSteps}" |
||||||
|
VerticalAlignment="Bottom" |
||||||
|
Width="64" /> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<Slider |
||||||
|
IsSnapToTickEnabled="True" |
||||||
|
Maximum="150" |
||||||
|
Minimum="0" |
||||||
|
TickFrequency="5" |
||||||
|
TickPlacement="BottomRight" |
||||||
|
Value="{Binding GenerationSteps}" /> |
||||||
|
</StackPanel> |
||||||
|
</ui:Card> |
||||||
|
</StackPanel> |
||||||
|
</ScrollViewer> |
||||||
|
|
||||||
|
<!-- Image Viewer --> |
||||||
|
<ui:Card |
||||||
|
Grid.Column="1" |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
Margin="8,8,8,16" |
||||||
|
Padding="8" |
||||||
|
VerticalAlignment="Top"> |
||||||
|
<Grid> |
||||||
|
<Grid.RowDefinitions> |
||||||
|
<RowDefinition Height="0.2*" /> |
||||||
|
<RowDefinition Height="0.7*" /> |
||||||
|
</Grid.RowDefinitions> |
||||||
|
<StackPanel |
||||||
|
Grid.Row="0" |
||||||
|
Orientation="Vertical" |
||||||
|
VerticalAlignment="Top"> |
||||||
|
<Label |
||||||
|
Content="Image" |
||||||
|
FontSize="14" |
||||||
|
Margin="2" /> |
||||||
|
<ProgressBar |
||||||
|
Maximum="100" |
||||||
|
Value="{Binding ProgressValue, FallbackValue=10}" |
||||||
|
Visibility="{Binding ProgressBarVisibility, FallbackValue=Visible}" |
||||||
|
Width="500" /> |
||||||
|
</StackPanel> |
||||||
|
<Grid |
||||||
|
Background="{DynamicResource ApplicationBackgroundBrush}" |
||||||
|
Grid.Row="1" |
||||||
|
MinHeight="512" |
||||||
|
VerticalAlignment="Stretch"> |
||||||
|
<ui:ProgressRing |
||||||
|
Background="DarkRed" |
||||||
|
Grid.Row="0" |
||||||
|
Height="72" |
||||||
|
HorizontalAlignment="Center" |
||||||
|
IsIndeterminate="True" |
||||||
|
Margin="8" |
||||||
|
VerticalAlignment="Center" |
||||||
|
Visibility="{Binding ProgressRingVisibility, FallbackValue=Collapsed}" |
||||||
|
Width="72" |
||||||
|
x:Name="ImageProgressRing" /> |
||||||
|
<Image |
||||||
|
HorizontalAlignment="Stretch" |
||||||
|
Margin="8" |
||||||
|
Source="{Binding ImagePreview}" |
||||||
|
Stretch="Uniform" |
||||||
|
VerticalAlignment="Stretch" |
||||||
|
Visibility="{Binding ImagePreviewVisibility, FallbackValue=Visible}" /> |
||||||
|
</Grid> |
||||||
|
</Grid> |
||||||
|
</ui:Card> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
<ui:Button |
||||||
|
Appearance="Primary" |
||||||
|
Command="{Binding TextToImageGenerateCommand}" |
||||||
|
Content="Generate Image" |
||||||
|
Grid.Row="1" |
||||||
|
HorizontalAlignment="Center" |
||||||
|
Name="TextToImageButton" /> |
||||||
|
</Grid> |
||||||
|
|
||||||
|
</Page> |
@ -0,0 +1,52 @@ |
|||||||
|
using System.Windows.Controls; |
||||||
|
using StabilityMatrix.ViewModels; |
||||||
|
using Wpf.Ui.Controls.AutoSuggestBoxControl; |
||||||
|
|
||||||
|
namespace StabilityMatrix; |
||||||
|
|
||||||
|
public sealed partial class TextToImagePage : Page |
||||||
|
{ |
||||||
|
private TextToImageViewModel ViewModel => (TextToImageViewModel) DataContext; |
||||||
|
|
||||||
|
public TextToImagePage(TextToImageViewModel viewModel) |
||||||
|
{ |
||||||
|
InitializeComponent(); |
||||||
|
DataContext = viewModel; |
||||||
|
} |
||||||
|
|
||||||
|
private void PositivePromptBox_OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
private void PositivePromptBox_OnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
private void PositivePromptBox_OnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) |
||||||
|
{ |
||||||
|
// Update the prompt text when the user types |
||||||
|
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) |
||||||
|
{ |
||||||
|
var fullText = sender.Text; |
||||||
|
ViewModel.PositivePromptText = fullText; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void NegativePromptBox_OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
private void NegativePromptBox_OnSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
private void NegativePromptBox_OnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) |
||||||
|
{ |
||||||
|
// Update the prompt text when the user types |
||||||
|
if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) |
||||||
|
{ |
||||||
|
var fullText = sender.Text; |
||||||
|
ViewModel.NegativePromptText = fullText; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Media.Imaging; |
||||||
|
using CommunityToolkit.Mvvm.ComponentModel; |
||||||
|
using CommunityToolkit.Mvvm.Input; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using NLog; |
||||||
|
using StabilityMatrix.Api; |
||||||
|
using StabilityMatrix.Helper; |
||||||
|
using StabilityMatrix.Models.Api; |
||||||
|
using ILogger = NLog.ILogger; |
||||||
|
|
||||||
|
namespace StabilityMatrix.ViewModels; |
||||||
|
|
||||||
|
public partial class TextToImageViewModel : ObservableObject |
||||||
|
{ |
||||||
|
private readonly ILogger<TextToImageViewModel> logger; |
||||||
|
private readonly IA3WebApi a3WebApi; |
||||||
|
private AsyncDispatcherTimer? progressQueryTimer; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private bool isGenerating; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
[NotifyPropertyChangedFor(nameof(ProgressRingVisibility))] |
||||||
|
[NotifyPropertyChangedFor(nameof(ImagePreviewVisibility))] |
||||||
|
private bool isProgressRingActive; |
||||||
|
|
||||||
|
public Visibility ProgressRingVisibility => IsProgressRingActive ? Visibility.Visible : Visibility.Collapsed; |
||||||
|
|
||||||
|
public Visibility ImagePreviewVisibility => IsProgressRingActive ? Visibility.Collapsed : Visibility.Visible; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
[NotifyPropertyChangedFor(nameof(ProgressBarVisibility))] |
||||||
|
private int progressValue; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string positivePromptText; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private string negativePromptText; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private int generationSteps; |
||||||
|
|
||||||
|
[ObservableProperty] |
||||||
|
private BitmapImage? imagePreview; |
||||||
|
|
||||||
|
public Visibility ProgressBarVisibility => ProgressValue > 0 ? Visibility.Visible : Visibility.Collapsed; |
||||||
|
|
||||||
|
public TextToImageViewModel(IA3WebApi a3WebApi, ILogger<TextToImageViewModel> logger) |
||||||
|
{ |
||||||
|
this.logger = logger; |
||||||
|
this.a3WebApi = a3WebApi; |
||||||
|
positivePromptText = "Positive"; |
||||||
|
negativePromptText = "Negative"; |
||||||
|
generationSteps = 10; |
||||||
|
} |
||||||
|
|
||||||
|
private void StartProgressTracking(TimeSpan? interval = null) |
||||||
|
{ |
||||||
|
progressQueryTimer = new AsyncDispatcherTimer |
||||||
|
{ |
||||||
|
Interval = interval ?? TimeSpan.FromMilliseconds(150), |
||||||
|
IsReentrant = false, |
||||||
|
TickTask = OnProgressTrackingTick, |
||||||
|
}; |
||||||
|
progressQueryTimer.Start(); |
||||||
|
} |
||||||
|
|
||||||
|
private void StopProgressTracking() |
||||||
|
{ |
||||||
|
IsProgressRingActive = false; |
||||||
|
ProgressValue = 0; |
||||||
|
progressQueryTimer?.Stop(); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task OnProgressTrackingTick() |
||||||
|
{ |
||||||
|
var request = new ProgressRequest(); |
||||||
|
var response = await a3WebApi.GetProgress(request); |
||||||
|
var progress = response.Progress; |
||||||
|
logger.LogInformation("Image Progress: {ResponseProgress}, ETA: {ResponseEtaRelative} s", response.Progress, response.EtaRelative); |
||||||
|
if (Math.Abs(progress - 1.0) < 0.01) |
||||||
|
{ |
||||||
|
ProgressValue = 100; |
||||||
|
progressQueryTimer?.Stop(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// Update progress |
||||||
|
ProgressValue = (int) Math.Clamp(Math.Ceiling(progress * 100), 0, 100); |
||||||
|
// Update preview image |
||||||
|
var result = response.CurrentImage; |
||||||
|
if (result != null) |
||||||
|
{ |
||||||
|
// Stop indeterminate progress ring |
||||||
|
IsProgressRingActive = false; |
||||||
|
// Set preview image |
||||||
|
var bitmap = Base64ToBitmap(result); |
||||||
|
ImagePreview = bitmap; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static BitmapImage Base64ToBitmap(string base64String) |
||||||
|
{ |
||||||
|
var imageBytes = Convert.FromBase64String(base64String); |
||||||
|
|
||||||
|
var bitmapImage = new BitmapImage(); |
||||||
|
bitmapImage.BeginInit(); |
||||||
|
|
||||||
|
using var ms = new MemoryStream(imageBytes, 0, imageBytes.Length); |
||||||
|
bitmapImage.StreamSource = ms; |
||||||
|
bitmapImage.CacheOption = BitmapCacheOption.OnLoad; |
||||||
|
bitmapImage.EndInit(); |
||||||
|
return bitmapImage; |
||||||
|
} |
||||||
|
|
||||||
|
[RelayCommand] |
||||||
|
private async void TextToImageGenerate() |
||||||
|
{ |
||||||
|
// Start indeterminate progress ring |
||||||
|
IsProgressRingActive = true; |
||||||
|
|
||||||
|
var request = new TextToImageRequest |
||||||
|
{ |
||||||
|
Prompt = PositivePromptText, |
||||||
|
NegativePrompt = NegativePromptText, |
||||||
|
Steps = GenerationSteps, |
||||||
|
}; |
||||||
|
var task = a3WebApi.TextToImage(request); |
||||||
|
|
||||||
|
// Progress track while waiting for response |
||||||
|
StartProgressTracking(); |
||||||
|
var response = await task; |
||||||
|
StopProgressTracking(); |
||||||
|
|
||||||
|
// Decode base64 image |
||||||
|
var result = response.Images[0]; |
||||||
|
var bitmap = Base64ToBitmap(result); |
||||||
|
|
||||||
|
ImagePreview = bitmap; |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue