Ionite
9 months ago
committed by
GitHub
42 changed files with 988 additions and 121 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; |
namespace StabilityMatrix.Avalonia.Models.Inference; |
||||||
|
|
||||||
[JsonSerializable(typeof(PromptCardModel))] |
|
||||||
public class PromptCardModel |
public class PromptCardModel |
||||||
{ |
{ |
||||||
public string? Prompt { get; set; } |
public string? Prompt { get; init; } |
||||||
public string? NegativePrompt { get; set; } |
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 interface IApiFactory |
||||||
{ |
{ |
||||||
public T CreateRefitClient<T>(Uri baseAddress); |
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; |
||||||
|
|
||||||
public abstract class NodeConnectionBase |
namespace StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
||||||
{ |
|
||||||
public object[]? Data { get; set; } |
|
||||||
|
|
||||||
// Implicit conversion to object[] |
[JsonConverter(typeof(NodeConnectionBaseJsonConverter))] |
||||||
public static implicit operator object[](NodeConnectionBase nodeConnection) |
public abstract class NodeConnectionBase |
||||||
{ |
{ |
||||||
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; |
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; |
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