Browse Source

Merge pull request #28 from ionite34/add-api-ui

pull/5/head
Ionite 1 year ago committed by GitHub
parent
commit
1ad7bb490d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .github/workflows/build.yml
  2. 20
      .github/workflows/remove-old-artifacts.yml
  3. 18
      StabilityMatrix/Api/IA3WebApi.cs
  4. 19
      StabilityMatrix/App.xaml.cs
  5. 50
      StabilityMatrix/Helper/AsyncDispatchTimer.cs
  6. 5
      StabilityMatrix/MainWindow.xaml
  7. 14
      StabilityMatrix/Models/Api/ImageResponse.cs
  8. 9
      StabilityMatrix/Models/Api/ProgressRequest.cs
  9. 24
      StabilityMatrix/Models/Api/ProgressResponse.cs
  10. 134
      StabilityMatrix/Models/Api/TextToImageRequest.cs
  11. 82
      StabilityMatrix/SettingsPage.xaml
  12. 157
      StabilityMatrix/TextToImagePage.xaml
  13. 52
      StabilityMatrix/TextToImagePage.xaml.cs
  14. 16
      StabilityMatrix/ViewModels/SettingsViewModel.cs
  15. 148
      StabilityMatrix/ViewModels/TextToImageViewModel.cs

3
.github/workflows/build.yml

@ -26,9 +26,10 @@ jobs:
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --output out --no-restore
run: dotnet publish -o out -c Release --no-restore --self-contained false -p:IncludeNativeLibrariesForSelfExtract=false
- name: Upload Artifacts
if: ${{ github.event_name == 'push' }}
uses: actions/upload-artifact@v2
with:
name: build

20
.github/workflows/remove-old-artifacts.yml

@ -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

18
StabilityMatrix/Api/IA3WebApi.cs

@ -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);
}

19
StabilityMatrix/App.xaml.cs

@ -2,6 +2,8 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -30,20 +32,37 @@ namespace StabilityMatrix
{
DispatcherUnhandledException += App_DispatcherUnhandledException;
}
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IPageService, PageService>();
serviceCollection.AddTransient<MainWindow>();
serviceCollection.AddTransient<SettingsPage>();
serviceCollection.AddTransient<LaunchPage>();
serviceCollection.AddTransient<InstallPage>();
serviceCollection.AddTransient<TextToImagePage>();
serviceCollection.AddTransient<MainWindowViewModel>();
serviceCollection.AddSingleton<SettingsViewModel>();
serviceCollection.AddSingleton<LaunchViewModel>();
serviceCollection.AddSingleton<InstallerViewModel>();
serviceCollection.AddSingleton<TextToImageViewModel>();
serviceCollection.AddSingleton<IContentDialogService, ContentDialogService>();
serviceCollection.AddSingleton<ISnackbarService, SnackbarService>();
serviceCollection.AddSingleton<ISettingsManager, SettingsManager>();
var jsonOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
var refitSettings = new RefitSettings
{
ContentSerializer = new SystemTextJsonContentSerializer(jsonOptions)
};
serviceCollection.AddRefitClient<IGithubApi>();
serviceCollection.AddRefitClient<IA3WebApi>(refitSettings).ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri("http://localhost:7860");
});
var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log.txt");
var logConfig = new NLog.Config.LoggingConfiguration();

50
StabilityMatrix/Helper/AsyncDispatchTimer.cs

@ -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; }
}

5
StabilityMatrix/MainWindow.xaml

@ -56,6 +56,11 @@
<ui:SymbolIcon Symbol="Wrench24" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="Text2Image" TargetPageType="{x:Type local:TextToImagePage}">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="Image24" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
</ui:NavigationView.MenuItems>
<ui:NavigationView.FooterMenuItems>
<ui:NavigationViewItem Content="Settings" TargetPageType="{x:Type local:SettingsPage}">

14
StabilityMatrix/Models/Api/ImageResponse.cs

@ -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; }
}

9
StabilityMatrix/Models/Api/ProgressRequest.cs

@ -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; }
}

24
StabilityMatrix/Models/Api/ProgressResponse.cs

@ -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; }
}

134
StabilityMatrix/Models/Api/TextToImageRequest.cs

@ -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; }
}

82
StabilityMatrix/SettingsPage.xaml

@ -1,38 +1,48 @@
<Page
Background="{DynamicResource ApplicationBackgroundBrush}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Loaded="SettingsPage_OnLoaded"
d:DataContext="{d:DesignInstance Type=viewModels:SettingsViewModel,
IsDesignTimeCreatable=True}"
d:DesignHeight="700"
d:DesignWidth="1100"
mc:Ignorable="d"
x:Class="StabilityMatrix.SettingsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:StabilityMatrix"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:StabilityMatrix"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:models="using:StabilityMatrix.Models"
d:DesignHeight="700"
d:DesignWidth="1100"
Loaded="SettingsPage_OnLoaded"
Background="{DynamicResource ApplicationBackgroundBrush}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
mc:Ignorable="d">
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Scroll view -->
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<Grid>
<StackPanel Orientation="Vertical">
<ui:Card Margin="8,16,8,8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Theme" FontWeight="Bold" FontSize="16" Margin="0,8" />
<StackPanel HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock
FontSize="16"
FontWeight="Bold"
Margin="0,8"
Text="Theme" />
<ComboBox
Margin="8"
ItemsSource="{Binding AvailableThemes}"
Margin="8"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Width="500" />
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Debug Info" FontWeight="Bold" FontSize="16" Margin="0,8" />
<StackPanel HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock
FontSize="16"
FontWeight="Bold"
Margin="0,8"
Text="Debug Info" />
<TextBlock Margin="8" Text="{Binding GpuInfo, FallbackValue=3dfx Voodoo 5 6000 - 128MB VRAM}" />
<TextBlock Margin="8" Text="{Binding GitInfo}" />
<TextBlock Margin="8" Text="{Binding TestProperty, FallbackValue=TestProperty}" />
@ -40,26 +50,50 @@
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Embedded Python" FontWeight="Bold" FontSize="16" Margin="0,8" />
<StackPanel HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock
FontSize="16"
FontWeight="Bold"
Margin="0,8"
Text="Embedded Python" />
<!-- Add a button to show messagebox for python version -->
<Button Margin="8" Content="Check Version Info" Command="{Binding PythonVersionCommand}"/>
<Button
Command="{Binding PythonVersionCommand}"
Content="Check Version Info"
Margin="8" />
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Debug" FontWeight="Bold" FontSize="16" Margin="0,8" />
<Button Margin="8" Content="Add Installation" Command="{Binding AddInstallationCommand}"/>
<StackPanel HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock
FontSize="16"
FontWeight="Bold"
Margin="0,8"
Text="Debug" />
<StackPanel Orientation="Horizontal">
<Button
Command="{Binding AddInstallationCommand}"
Content="Add Installation"
Margin="8" />
<Button
Command="{Binding PingWebApiCommand}"
Content="Ping Web API"
Margin="8" />
</StackPanel>
</StackPanel>
</ui:Card>
<ui:Card Margin="8">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="Some Other Setting" FontWeight="Bold" FontSize="16" Margin="0,8" />
<StackPanel HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock
FontSize="16"
FontWeight="Bold"
Margin="0,8"
Text="Some Other Setting" />
<ComboBox
Margin="8"
ItemsSource="{Binding AvailableThemes}"
Margin="8"
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}"
Width="500" />
</StackPanel>

157
StabilityMatrix/TextToImagePage.xaml

@ -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>

52
StabilityMatrix/TextToImagePage.xaml.cs

@ -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;
}
}
}

16
StabilityMatrix/ViewModels/SettingsViewModel.cs

@ -6,6 +6,7 @@ using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Ookii.Dialogs.Wpf;
using StabilityMatrix.Api;
using StabilityMatrix.Helper;
using StabilityMatrix.Models;
using Wpf.Ui.Appearance;
@ -24,11 +25,13 @@ public partial class SettingsViewModel : ObservableObject
"System",
};
private readonly IContentDialogService contentDialogService;
private readonly IA3WebApi a3WebApi;
public SettingsViewModel(ISettingsManager settingsManager, IContentDialogService contentDialogService)
public SettingsViewModel(ISettingsManager settingsManager, IContentDialogService contentDialogService, IA3WebApi a3WebApi)
{
this.settingsManager = settingsManager;
this.contentDialogService = contentDialogService;
this.a3WebApi = a3WebApi;
SelectedTheme = settingsManager.Settings.Theme ?? "Dark";
}
@ -101,6 +104,17 @@ public partial class SettingsViewModel : ObservableObject
settingsManager.AddInstalledPackage(package);
});
[RelayCommand]
private async Task PingWebApi()
{
var result = await a3WebApi.GetPing();
var dialog = contentDialogService.CreateDialog();
dialog.Title = "Web API ping";
dialog.Content = result;
dialog.PrimaryButtonText = "Ok";
await dialog.ShowAsync();
}
public async Task OnLoaded()
{
SelectedTheme = string.IsNullOrWhiteSpace(settingsManager.Settings.Theme)

148
StabilityMatrix/ViewModels/TextToImageViewModel.cs

@ -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…
Cancel
Save