Ionite
9 months ago
committed by
GitHub
38 changed files with 940 additions and 94 deletions
@ -0,0 +1,96 @@
|
||||
<Styles |
||||
xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
xmlns:controls="using:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" |
||||
xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core" |
||||
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" |
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" |
||||
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia" |
||||
xmlns:fluentIcons="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent" |
||||
xmlns:local="clr-namespace:StabilityMatrix.Avalonia" |
||||
x:DataType="inference:PromptExpansionCardViewModel"> |
||||
|
||||
<Design.PreviewWith> |
||||
<Panel Width="400" Height="200"> |
||||
<StackPanel Width="300" VerticalAlignment="Center"> |
||||
<controls:PromptExpansionCard /> |
||||
</StackPanel> |
||||
</Panel> |
||||
</Design.PreviewWith> |
||||
|
||||
<Style Selector="controls|PromptExpansionCard"> |
||||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
||||
<Setter Property="Template"> |
||||
<ControlTemplate> |
||||
<controls:Card Padding="12"> |
||||
<sg:SpacedGrid |
||||
ColumnDefinitions="Auto,*" |
||||
ColumnSpacing="8" |
||||
RowDefinitions="*,*,*,*" |
||||
RowSpacing="0"> |
||||
<!-- Model --> |
||||
<TextBlock |
||||
Grid.Column="0" |
||||
VerticalAlignment="Center" |
||||
Text="{x:Static lang:Resources.Label_Model}" |
||||
TextAlignment="Left" /> |
||||
|
||||
<ui:FAComboBox |
||||
x:Name="PART_ModelComboBox" |
||||
Grid.Row="0" |
||||
Grid.Column="1" |
||||
HorizontalAlignment="Stretch" |
||||
ItemContainerTheme="{StaticResource FAComboBoxItemHybridModelTheme}" |
||||
ItemsSource="{Binding ClientManager.PromptExpansionModels}" |
||||
SelectedItem="{Binding SelectedModel}"> |
||||
|
||||
<ui:FAComboBox.Resources> |
||||
<input:StandardUICommand x:Key="RemoteDownloadCommand" |
||||
Command="{Binding RemoteDownloadCommand}" /> |
||||
</ui:FAComboBox.Resources> |
||||
|
||||
<ui:FAComboBox.DataTemplates> |
||||
<controls:HybridModelTemplateSelector> |
||||
<DataTemplate x:Key="{x:Static models:HybridModelType.Downloadable}" DataType="models:HybridModelFile"> |
||||
<Grid ColumnDefinitions="*,Auto"> |
||||
<TextBlock Foreground="{DynamicResource ThemeGreyColor}" Text="{Binding ShortDisplayName}" /> |
||||
<Button |
||||
Grid.Column="1" |
||||
Margin="8,0,0,0" |
||||
Padding="0" |
||||
Classes="transparent-full"> |
||||
<fluentIcons:SymbolIcon |
||||
VerticalAlignment="Center" |
||||
FontSize="18" |
||||
Foreground="{DynamicResource ThemeGreyColor}" |
||||
IsFilled="True" |
||||
Symbol="CloudArrowDown" /> |
||||
</Button> |
||||
</Grid> |
||||
</DataTemplate> |
||||
<DataTemplate x:Key="{x:Static models:HybridModelType.None}" DataType="models:HybridModelFile"> |
||||
<TextBlock Text="{Binding ShortDisplayName}" /> |
||||
</DataTemplate> |
||||
</controls:HybridModelTemplateSelector> |
||||
</ui:FAComboBox.DataTemplates> |
||||
|
||||
</ui:FAComboBox> |
||||
|
||||
<!--<controls:BetterComboBox |
||||
Grid.Row="0" |
||||
Grid.Column="1" |
||||
Padding="8,6,4,6" |
||||
HorizontalAlignment="Stretch" |
||||
ItemsSource="{Binding ClientManager.Upscalers}" |
||||
SelectedItem="{Binding SelectedModel}" |
||||
Theme="{StaticResource BetterComboBoxHybridModelTheme}" />--> |
||||
</sg:SpacedGrid> |
||||
</controls:Card> |
||||
</ControlTemplate> |
||||
</Setter> |
||||
</Style> |
||||
</Styles> |
@ -0,0 +1,52 @@
|
||||
using AsyncAwaitBestPractices; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Controls.Primitives; |
||||
using FluentAvalonia.UI.Controls; |
||||
using StabilityMatrix.Avalonia.ViewModels.Inference; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Models; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Controls; |
||||
|
||||
[Transient] |
||||
public class PromptExpansionCard : TemplatedControl |
||||
{ |
||||
/// <inheritdoc /> |
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
||||
{ |
||||
base.OnApplyTemplate(e); |
||||
|
||||
var upscalerComboBox = e.NameScope.Find("PART_ModelComboBox") as FAComboBox; |
||||
upscalerComboBox!.SelectionChanged += UpscalerComboBox_OnSelectionChanged; |
||||
} |
||||
|
||||
private void UpscalerComboBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) |
||||
{ |
||||
if (e.AddedItems.Count == 0) |
||||
return; |
||||
|
||||
var item = e.AddedItems[0]; |
||||
if (item is HybridModelFile { IsDownloadable: true }) |
||||
{ |
||||
// Reset the selection |
||||
e.Handled = true; |
||||
|
||||
if ( |
||||
e.RemovedItems.Count > 0 |
||||
&& e.RemovedItems[0] is HybridModelFile { IsDownloadable: false } removedItem |
||||
) |
||||
{ |
||||
(sender as FAComboBox)!.SelectedItem = removedItem; |
||||
} |
||||
else |
||||
{ |
||||
(sender as FAComboBox)!.SelectedItem = null; |
||||
} |
||||
|
||||
// Show dialog to download the model |
||||
(DataContext as PromptExpansionCardViewModel)! |
||||
.RemoteDownloadCommand.ExecuteAsync(item) |
||||
.SafeFireAndForget(); |
||||
} |
||||
} |
||||
} |
@ -1,10 +1,10 @@
|
||||
using System.Text.Json.Serialization; |
||||
using System.Text.Json.Nodes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Models.Inference; |
||||
|
||||
[JsonSerializable(typeof(PromptCardModel))] |
||||
public class PromptCardModel |
||||
{ |
||||
public string? Prompt { get; set; } |
||||
public string? NegativePrompt { get; set; } |
||||
public string? Prompt { get; init; } |
||||
public string? NegativePrompt { get; init; } |
||||
public JsonObject? ModulesCardState { get; init; } |
||||
} |
||||
|
@ -0,0 +1,40 @@
|
||||
using System; |
||||
using StabilityMatrix.Avalonia.Models.Inference; |
||||
using StabilityMatrix.Avalonia.Services; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Models.Api.Comfy.Nodes; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Inference.Modules; |
||||
|
||||
[ManagedService] |
||||
[Transient] |
||||
public class PromptExpansionModule : ModuleBase |
||||
{ |
||||
public PromptExpansionModule(ServiceManager<ViewModelBase> vmFactory) |
||||
: base(vmFactory) |
||||
{ |
||||
Title = "Prompt Expansion"; |
||||
AddCards(vmFactory.Get<PromptExpansionCardViewModel>()); |
||||
} |
||||
|
||||
protected override void OnApplyStep(ModuleApplyStepEventArgs e) |
||||
{ |
||||
var promptExpansionCard = GetCard<PromptExpansionCardViewModel>(); |
||||
|
||||
var model = |
||||
promptExpansionCard.SelectedModel |
||||
?? throw new InvalidOperationException($"{Title}: Model not selected"); |
||||
|
||||
e.Builder.Connections.PositivePrompt = e.Nodes.AddTypedNode( |
||||
new ComfyNodeBuilder.PromptExpansion |
||||
{ |
||||
Name = e.Nodes.GetUniqueName("PromptExpansion_Positive"), |
||||
ModelName = model.RelativePath, |
||||
Text = e.Builder.Connections.PositivePrompt, |
||||
Seed = e.Builder.Connections.Seed, |
||||
LogPrompt = promptExpansionCard.IsLogOutputEnabled |
||||
} |
||||
).Output; |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
using System.Threading.Tasks; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using CommunityToolkit.Mvvm.Input; |
||||
using FluentAvalonia.UI.Controls; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
using StabilityMatrix.Avalonia.Services; |
||||
using StabilityMatrix.Avalonia.ViewModels.Base; |
||||
using StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Models; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Inference; |
||||
|
||||
[View(typeof(PromptExpansionCard))] |
||||
[ManagedService] |
||||
[Transient] |
||||
public partial class PromptExpansionCardViewModel( |
||||
IInferenceClientManager clientManager, |
||||
ServiceManager<ViewModelBase> vmFactory |
||||
) : LoadableViewModelBase |
||||
{ |
||||
public const string ModuleKey = "PromptExpansion"; |
||||
|
||||
public IInferenceClientManager ClientManager { get; } = clientManager; |
||||
|
||||
[ObservableProperty] |
||||
private HybridModelFile? selectedModel; |
||||
|
||||
[ObservableProperty] |
||||
private bool isLogOutputEnabled = true; |
||||
|
||||
[RelayCommand] |
||||
private async Task RemoteDownload(HybridModelFile? model) |
||||
{ |
||||
if (model?.DownloadableResource is not { } resource) |
||||
return; |
||||
|
||||
var confirmDialog = vmFactory.Get<DownloadResourceViewModel>(); |
||||
confirmDialog.Resource = resource; |
||||
confirmDialog.FileName = resource.FileName; |
||||
|
||||
if (await confirmDialog.GetDialog().ShowAsync() == ContentDialogResult.Primary) |
||||
{ |
||||
confirmDialog.StartDownload(); |
||||
} |
||||
} |
||||
} |
@ -1,6 +1,10 @@
|
||||
namespace StabilityMatrix.Core.Api; |
||||
using Refit; |
||||
|
||||
namespace StabilityMatrix.Core.Api; |
||||
|
||||
public interface IApiFactory |
||||
{ |
||||
public T CreateRefitClient<T>(Uri baseAddress); |
||||
|
||||
public T CreateRefitClient<T>(Uri baseAddress, RefitSettings refitSettings); |
||||
} |
||||
|
@ -0,0 +1,14 @@
|
||||
using StabilityMatrix.Core.Models.Api.Comfy.Nodes; |
||||
|
||||
namespace StabilityMatrix.Core.Attributes; |
||||
|
||||
/// <summary> |
||||
/// Options for <see cref="ComfyTypedNodeBase{TOutput}"/> |
||||
/// </summary> |
||||
[AttributeUsage(AttributeTargets.Class)] |
||||
public class TypedNodeOptionsAttribute : Attribute |
||||
{ |
||||
public string? Name { get; init; } |
||||
|
||||
public string[]? RequiredExtensions { get; init; } |
||||
} |
@ -0,0 +1,49 @@
|
||||
using System.Reflection; |
||||
using System.Text.Json; |
||||
using System.Text.Json.Serialization; |
||||
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
||||
|
||||
namespace StabilityMatrix.Core.Converters.Json; |
||||
|
||||
public class NodeConnectionBaseJsonConverter : JsonConverter<NodeConnectionBase> |
||||
{ |
||||
/// <inheritdoc /> |
||||
public override NodeConnectionBase Read( |
||||
ref Utf8JsonReader reader, |
||||
Type typeToConvert, |
||||
JsonSerializerOptions options |
||||
) |
||||
{ |
||||
// Read as Data array |
||||
reader.Read(); |
||||
var data = new object[2]; |
||||
reader.Read(); |
||||
data[0] = reader.GetString() ?? throw new JsonException("Expected string for node name"); |
||||
reader.Read(); |
||||
data[1] = reader.GetInt32(); |
||||
reader.Read(); |
||||
|
||||
if (Activator.CreateInstance(typeToConvert) is not NodeConnectionBase instance) |
||||
{ |
||||
throw new JsonException($"Failed to create instance of {typeToConvert}"); |
||||
} |
||||
|
||||
var propertyInfo = |
||||
typeToConvert.GetProperty("Data", BindingFlags.Public | BindingFlags.Instance) |
||||
?? throw new JsonException($"Failed to get Data property of {typeToConvert}"); |
||||
|
||||
propertyInfo.SetValue(instance, data); |
||||
|
||||
return instance; |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override void Write(Utf8JsonWriter writer, NodeConnectionBase value, JsonSerializerOptions options) |
||||
{ |
||||
// Write as Data array |
||||
writer.WriteStartArray(); |
||||
writer.WriteStringValue(value.Data?[0] as string); |
||||
writer.WriteNumberValue((int)value.Data?[1]!); |
||||
writer.WriteEndArray(); |
||||
} |
||||
} |
@ -0,0 +1,63 @@
|
||||
using System.Text.Json; |
||||
using System.Text.Json.Serialization; |
||||
using OneOf; |
||||
|
||||
namespace StabilityMatrix.Core.Converters.Json; |
||||
|
||||
public class OneOfJsonConverter<T1, T2> : JsonConverter<OneOf<T1, T2>> |
||||
{ |
||||
/// <inheritdoc /> |
||||
public override OneOf<T1, T2> Read( |
||||
ref Utf8JsonReader reader, |
||||
Type typeToConvert, |
||||
JsonSerializerOptions options |
||||
) |
||||
{ |
||||
// Not sure how else to do this without polymorphic type markers but that would not serialize into T1/T2 |
||||
// So just try to deserialize T1, if it fails, try T2 |
||||
Exception? t1Exception = null; |
||||
Exception? t2Exception = null; |
||||
|
||||
try |
||||
{ |
||||
if (JsonSerializer.Deserialize<T1>(ref reader, options) is { } t1) |
||||
{ |
||||
return t1; |
||||
} |
||||
} |
||||
catch (JsonException e) |
||||
{ |
||||
t1Exception = e; |
||||
} |
||||
|
||||
try |
||||
{ |
||||
if (JsonSerializer.Deserialize<T2>(ref reader, options) is { } t2) |
||||
{ |
||||
return t2; |
||||
} |
||||
} |
||||
catch (JsonException e) |
||||
{ |
||||
t2Exception = e; |
||||
} |
||||
|
||||
throw new JsonException( |
||||
$"Failed to deserialize OneOf<{typeof(T1)}, {typeof(T2)}> as either {typeof(T1)} or {typeof(T2)}", |
||||
new AggregateException([t1Exception, t2Exception]) |
||||
); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override void Write(Utf8JsonWriter writer, OneOf<T1, T2> value, JsonSerializerOptions options) |
||||
{ |
||||
if (value.IsT0) |
||||
{ |
||||
JsonSerializer.Serialize(writer, value.AsT0, options); |
||||
} |
||||
else |
||||
{ |
||||
JsonSerializer.Serialize(writer, value.AsT1, options); |
||||
} |
||||
} |
||||
} |
@ -1,12 +1,14 @@
|
||||
namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
||||
using System.Text.Json.Serialization; |
||||
using StabilityMatrix.Core.Converters.Json; |
||||
|
||||
namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
||||
|
||||
[JsonConverter(typeof(NodeConnectionBaseJsonConverter))] |
||||
public abstract class NodeConnectionBase |
||||
{ |
||||
public object[]? Data { get; set; } |
||||
|
||||
// Implicit conversion to object[] |
||||
public static implicit operator object[](NodeConnectionBase nodeConnection) |
||||
{ |
||||
return nodeConnection.Data ?? Array.Empty<object>(); |
||||
} |
||||
/// <summary> |
||||
/// Array data for the connection. |
||||
/// [(string) Node Name, (int) Connection Index] |
||||
/// </summary> |
||||
public object[]? Data { get; init; } |
||||
} |
||||
|
@ -1,25 +1,27 @@
|
||||
namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
||||
|
||||
public class LatentNodeConnection : NodeConnectionBase { } |
||||
public class LatentNodeConnection : NodeConnectionBase; |
||||
|
||||
public class VAENodeConnection : NodeConnectionBase { } |
||||
public class VAENodeConnection : NodeConnectionBase; |
||||
|
||||
public class ImageNodeConnection : NodeConnectionBase { } |
||||
public class ImageNodeConnection : NodeConnectionBase; |
||||
|
||||
public class ImageMaskConnection : NodeConnectionBase { } |
||||
public class ImageMaskConnection : NodeConnectionBase; |
||||
|
||||
public class UpscaleModelNodeConnection : NodeConnectionBase { } |
||||
public class UpscaleModelNodeConnection : NodeConnectionBase; |
||||
|
||||
public class ModelNodeConnection : NodeConnectionBase { } |
||||
public class ModelNodeConnection : NodeConnectionBase; |
||||
|
||||
public class ConditioningNodeConnection : NodeConnectionBase { } |
||||
public class ConditioningNodeConnection : NodeConnectionBase; |
||||
|
||||
public class ClipNodeConnection : NodeConnectionBase { } |
||||
public class ClipNodeConnection : NodeConnectionBase; |
||||
|
||||
public class ControlNetNodeConnection : NodeConnectionBase { } |
||||
public class ControlNetNodeConnection : NodeConnectionBase; |
||||
|
||||
public class ClipVisionNodeConnection : NodeConnectionBase { } |
||||
public class ClipVisionNodeConnection : NodeConnectionBase; |
||||
|
||||
public class SamplerNodeConnection : NodeConnectionBase { } |
||||
public class SamplerNodeConnection : NodeConnectionBase; |
||||
|
||||
public class SigmasNodeConnection : NodeConnectionBase { } |
||||
public class SigmasNodeConnection : NodeConnectionBase; |
||||
|
||||
public class StringNodeConnection : NodeConnectionBase; |
||||
|
@ -1,3 +1,6 @@
|
||||
namespace StabilityMatrix.Core.Models.Packages.Extensions; |
||||
|
||||
public record ExtensionManifest(Uri Uri); |
||||
public record ExtensionManifest(Uri Uri) |
||||
{ |
||||
public static implicit operator ExtensionManifest(string uri) => new(new Uri(uri, UriKind.Absolute)); |
||||
} |
||||
|
Loading…
Reference in new issue