diff --git a/Diffusion-macOS/ControlsView.swift b/Diffusion-macOS/ControlsView.swift index f36ef25..b697b7c 100644 --- a/Diffusion-macOS/ControlsView.swift +++ b/Diffusion-macOS/ControlsView.swift @@ -65,6 +65,12 @@ struct ControlsView: View { // TODO: make this computed, and observable, and easy to read @State private var mustShowSafetyCheckerDisclaimer = false + @State private var showModelsHelp = false + @State private var showPromptsHelp = false + @State private var showGuidanceHelp = false + @State private var showStepsHelp = false + @State private var showSeedHelp = false + func updateSafetyCheckerState() { mustShowSafetyCheckerDisclaimer = generation.disableSafety && !Settings.shared.safetyCheckerDisclaimerShown } @@ -110,7 +116,7 @@ struct ControlsView: View { let prefix = downloaded ? "โ— " : "โ—Œ " //"โ—‹ " return Text(prefix).foregroundColor(downloaded ? .accentColor : .secondary) + Text(model.modelVersion) } - + var body: some View { VStack(alignment: .leading) { @@ -132,9 +138,23 @@ struct ControlsView: View { modelDidChange(model: model) } } label: { - Label("Model from Hub", systemImage: "cpu").foregroundColor(.secondary) + HStack { + Label("Model from Hub", systemImage: "cpu").foregroundColor(.secondary) + Spacer() + if disclosedModel { + Button { + showModelsHelp.toggle() + } label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) + // Or maybe use .sheet instead + .sheet(isPresented: $showModelsHelp) { + modelsHelp($showModelsHelp) + } + } + }.foregroundColor(.secondary) } - Divider() DisclosureGroup(isExpanded: $disclosedPrompt) { @@ -148,19 +168,51 @@ struct ControlsView: View { .textFieldStyle(.squareBorder) }.padding(.leading, 10) } label: { - Label("Prompts", systemImage: "text.quote").foregroundColor(.secondary) + HStack { + Label("Prompts", systemImage: "text.quote").foregroundColor(.secondary) + Spacer() + if disclosedPrompt { + Button { + showPromptsHelp.toggle() + } label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) + // Or maybe use .sheet instead + .popover(isPresented: $showPromptsHelp, arrowEdge: .trailing) { + promptsHelp($showPromptsHelp) + } + } + }.foregroundColor(.secondary) } - Divider() + let guidanceScaleValue = generation.guidanceScale.formatted("%.1f") DisclosureGroup(isExpanded: $disclosedGuidance) { CompactSlider(value: $generation.guidanceScale, in: 0...20, step: 0.5) { Text("Guidance Scale") Spacer() - Text(generation.guidanceScale.formatted("%.1f")) + Text(guidanceScaleValue) }.padding(.leading, 10) } label: { - Label("Guidance Scale", systemImage: "scalemass").foregroundColor(.secondary) + HStack { + Label("Guidance Scale", systemImage: "scalemass").foregroundColor(.secondary) + Spacer() + if disclosedGuidance { + Button { + showGuidanceHelp.toggle() + } label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) + // Or maybe use .sheet instead + .popover(isPresented: $showGuidanceHelp, arrowEdge: .trailing) { + guidanceHelp($showGuidanceHelp) + } + } else { + Text(guidanceScaleValue) + } + }.foregroundColor(.secondary) } Divider() @@ -171,7 +223,23 @@ struct ControlsView: View { Text("\(Int(generation.steps))") }.padding(.leading, 10) } label: { - Label("Step count", systemImage: "square.3.layers.3d.down.left").foregroundColor(.secondary) + HStack { + Label("Step count", systemImage: "square.3.layers.3d.down.left").foregroundColor(.secondary) + Spacer() + if disclosedSteps { + Button { + showStepsHelp.toggle() + } label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) + .popover(isPresented: $showStepsHelp, arrowEdge: .trailing) { + stepsHelp($showStepsHelp) + } + } else { + Text("\(Int(generation.steps))") + } + }.foregroundColor(.secondary) } Divider() @@ -183,7 +251,23 @@ struct ControlsView: View { Text("\(Int(generation.seed))") }.padding(.leading, 10) } label: { - Label("Seed", systemImage: "leaf").foregroundColor(.secondary) + HStack { + Label("Seed", systemImage: "leaf").foregroundColor(.secondary) + Spacer() + if disclosedSeed { + Button { + showSeedHelp.toggle() + } label: { + Image(systemName: "info.circle") + } + .buttonStyle(.plain) + .popover(isPresented: $showSeedHelp, arrowEdge: .trailing) { + seedHelp($showSeedHelp) + } + } else { + Text("\(Int(generation.seed))") + } + }.foregroundColor(.secondary) } } } @@ -224,4 +308,3 @@ struct ControlsView: View { } } } - diff --git a/Diffusion-macOS/HelpContent.swift b/Diffusion-macOS/HelpContent.swift new file mode 100644 index 0000000..9e3e397 --- /dev/null +++ b/Diffusion-macOS/HelpContent.swift @@ -0,0 +1,125 @@ +// +// HelpContent.swift +// Diffusion-macOS +// +// Created by Pedro Cuenca on 7/2/23. +// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE +// + +import SwiftUI + +func helpContent(title: String, description: Text, showing: Binding, width: Double = 400) -> some View { + VStack { + Text(title) + .font(.title3) + .padding(.top, 10) + .padding(.all, 5) + description + .lineLimit(nil) + .padding(.bottom, 5) + .padding([.leading, .trailing], 15) + Button { + showing.wrappedValue.toggle() + } label: { + Text("Dismiss").frame(maxWidth: 200) + } + .padding(.bottom) + } + .frame(minWidth: width, idealWidth: width, maxWidth: width) +} + +func helpContent(title: String, description: String, showing: Binding, width: Double = 400) -> some View { + helpContent(title: title, description: Text(description), showing: showing) +} + +func helpContent(title: String, description: AttributedString, showing: Binding, width: Double = 400) -> some View { + helpContent(title: title, description: Text(description), showing: showing) +} + + +func modelsHelp(_ showing: Binding) -> some View { + let description = try! AttributedString(markdown: + """ + Diffusers launches with a set of 5 models that can be downloaded from the Hugging Face Hub: + + **Stable Diffusion 1.4** + + This is the original Stable Diffusion model that changed the landscape of AI image generation. + + **Stable Diffusion 1.5** + + Same architecture as 1.4, but trained on additional images with a focus on aesthetics. + + **Stable Diffusion 2** + + Improved model, heavily retrained on millions of additional images. + + **Stable Diffusion 2.1** + + The last reference in the Stable Diffusion family. Works great with _negative prompts_. + + OFA small v0 + + This is a special so-called _distilled_ model, half the size of the others. It runs faster and requires less RAM, try it out if you find generation slow! + + """, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) + return helpContent(title: "Available Models", description: description, showing: showing, width: 600) +} + +func promptsHelp(_ showing: Binding) -> some View { + let description = try! AttributedString(markdown: + """ + **Prompt** is the description of what you want, and **negative prompt** is what you _don't want_. + + Use the negative prompt to tweak a previous generation (by removing unwanted items), or to provide hints for the model. + + Many people like to use negative prompts such as "ugly, bad quality" to make the model try harder. \ + Or consider excluding terms like "3d" or "realistic" if you're after particular drawing styles. + + """, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) + return helpContent(title: "Prompt and Negative Prompt", description: description, showing: showing, width: 600) +} + +func guidanceHelp(_ showing: Binding) -> some View { + let description = + """ + Indicates how much the image should resemble the prompt. + + Low values produce more varied results, while excessively high ones \ + may result in image artifacts such as posterization. + + Values between 7 and 10 are usually good choices, but they affect \ + differently to different models. + + Feel free to experiment! + """ + return helpContent(title: "Guidance Scale", description: description, showing: showing) +} + +func stepsHelp(_ showing: Binding) -> some View { + let description = + """ + How many times to go through the diffusion process. + + Quality increases the more steps you choose, but marginal improvements \ + get increasingly smaller. + + ๐Ÿงจ Diffusers currently uses the super efficient DPM Solver scheduler, \ + which produces great results in just 20 or 25 steps ๐Ÿคฏ + """ + return helpContent(title: "Inference Steps", description: description, showing: showing) +} + +func seedHelp(_ showing: Binding) -> some View { + let description = + """ + This is a number that allows you to reproduce a previous generation. + + Use it like this: select a seed and write a prompt, then generate an image. \ + Next, maybe add a negative prompt or tweak the prompt slightly, and see how the result changes. \ + Rinse and repeat until you are satisfied, or select a new seed to start over. + + If you select -1, a random seed will be chosen for you. + """ + return helpContent(title: "Generation Seed", description: description, showing: showing) +} diff --git a/Diffusion.xcodeproj/project.pbxproj b/Diffusion.xcodeproj/project.pbxproj index dc9d76f..d583297 100644 --- a/Diffusion.xcodeproj/project.pbxproj +++ b/Diffusion.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + EB067F872992E561004D1AD9 /* HelpContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB067F862992E561004D1AD9 /* HelpContent.swift */; }; EB33A51D2954D89F00B16357 /* StableDiffusion in Frameworks */ = {isa = PBXBuildFile; productRef = EB33A51C2954D89F00B16357 /* StableDiffusion */; }; EBB5BA5329425BEE003A2A5B /* PipelineLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */; }; EBB5BA5829425E17003A2A5B /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = EBB5BA5729425E17003A2A5B /* Path */; }; @@ -61,6 +62,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + EB067F862992E561004D1AD9 /* HelpContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpContent.swift; sourceTree = ""; }; EB33A51E2954E1BC00B16357 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; EBB5BA5229425BEE003A2A5B /* PipelineLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PipelineLoader.swift; sourceTree = ""; }; EBB5BA5929426E06003A2A5B /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; @@ -244,6 +246,7 @@ EBDD7DBB2977FFB300C1C4B2 /* GeneratedImageView.swift */, F1552030297109C300DC009B /* ControlsView.swift */, F155203329710B3600DC009B /* StatusView.swift */, + EB067F862992E561004D1AD9 /* HelpContent.swift */, F155202C2971093400DC009B /* Diffusion_macOS.entitlements */, F15520292971093400DC009B /* Preview Content */, ); @@ -472,6 +475,7 @@ EBDD7DAA29731F6C00C1C4B2 /* Pipeline.swift in Sources */, F15520262971093300DC009B /* ContentView.swift in Sources */, EBDD7DB92976AAFE00C1C4B2 /* State.swift in Sources */, + EB067F872992E561004D1AD9 /* HelpContent.swift in Sources */, EBDD7DB42973200200C1C4B2 /* Utils.swift in Sources */, F1552031297109C300DC009B /* ControlsView.swift in Sources */, EBDD7DB62973206600C1C4B2 /* Downloader.swift in Sources */,