[
- state.settings,
- state.updateSettings,
- state.fileManagerState,
- state.updateFileManagerState,
- ])
+ const [
+ updateAppState,
+ settings,
+ updateSettings,
+ fileManagerState,
+ updateFileManagerState,
+ ] = useStore((state) => [
+ state.updateAppState,
+ state.settings,
+ state.updateSettings,
+ state.fileManagerState,
+ state.updateFileManagerState,
+ ])
const { toast } = useToast()
const [model, setModel] = useState
(settings.model)
@@ -110,6 +116,7 @@ export function SettingsDialog() {
})
if (model.name !== settings.model.name) {
toggleOpenModelSwitching()
+ updateAppState({ disableShortCuts: true })
switchModel(model.name)
.then((res) => {
if (res.ok) {
@@ -126,14 +133,16 @@ export function SettingsDialog() {
variant: "destructive",
title: `Switch to ${model.name} failed`,
})
+ setModel(settings.model)
})
.finally(() => {
toggleOpenModelSwitching()
+ updateAppState({ disableShortCuts: false })
})
}
}
- useHotkeys("s", () => {
+ useHotKey("s", () => {
toggleOpen()
onSubmit(form.getValues())
})
@@ -183,6 +192,12 @@ export function SettingsDialog() {
for (let info of modelInfos) {
if (model.name === info.name) {
defaultTab = info.model_type
+ if (defaultTab === MODEL_TYPE_DIFFUSERS_SDXL) {
+ defaultTab = MODEL_TYPE_DIFFUSERS_SD
+ }
+ if (defaultTab === MODEL_TYPE_DIFFUSERS_SDXL_INPAINT) {
+ defaultTab = MODEL_TYPE_DIFFUSERS_SD_INPAINT
+ }
break
}
}
@@ -384,9 +399,12 @@ export function SettingsDialog() {
-
- TODO: 添加加载动画 Switching to {model.name}
-
+ {/* */}
+
+
logo
+
Switching to {model.name}
+
+ {/* */}
@@ -434,7 +452,7 @@ export function SettingsDialog() {
)} */}
-
+
diff --git a/web_app/src/components/Shortcuts.tsx b/web_app/src/components/Shortcuts.tsx
index 9e3860c..d88a1a3 100644
--- a/web_app/src/components/Shortcuts.tsx
+++ b/web_app/src/components/Shortcuts.tsx
@@ -8,7 +8,7 @@ import {
DialogTitle,
DialogTrigger,
} from "./ui/dialog"
-import { useHotkeys } from "react-hotkeys-hook"
+import useHotKey from "@/hooks/useHotkey"
interface ShortcutProps {
content: string
@@ -48,7 +48,7 @@ const CmdOrCtrl = () => {
export function Shortcuts() {
const [open, toggleOpen] = useToggle(false)
- useHotkeys("h", () => {
+ useHotKey("h", () => {
toggleOpen()
})
diff --git a/web_app/src/components/SidePanel.tsx b/web_app/src/components/SidePanel.tsx
index b6af9e5..d5cc3eb 100644
--- a/web_app/src/components/SidePanel.tsx
+++ b/web_app/src/components/SidePanel.tsx
@@ -1,7 +1,6 @@
import { FormEvent, useState } from "react"
import { useToggle, useWindowSize } from "react-use"
import { useStore } from "@/lib/states"
-import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
import { Switch } from "./ui/switch"
import { Label } from "./ui/label"
import { NumberInput } from "./ui/input"
@@ -15,38 +14,36 @@ import {
} from "./ui/select"
import { Textarea } from "./ui/textarea"
import { SDSampler } from "@/lib/types"
-import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
-} from "./ui/accordion"
import { Separator } from "./ui/separator"
-import { useHotkeys } from "react-hotkeys-hook"
import { ScrollArea } from "./ui/scroll-area"
import {
Sheet,
SheetContent,
- SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "./ui/sheet"
-import { ChevronLeft } from "lucide-react"
+import { ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "./ui/button"
+import useHotKey from "@/hooks/useHotkey"
+import { Slider } from "./ui/slider"
+
+const RowContainer = ({ children }: { children: React.ReactNode }) => (
+
-
-
-
+
+
+
+ {
+ updateSettings({ enableControlNet: value })
+ }}
+ />
+
+
+
+
+
-
-
+
+
{
updateSettings({ controlnetConditioningScale: value })
}}
/>
-
+
+
+
)
}
@@ -120,16 +133,19 @@ const SidePanel = () => {
}
return (
-
-
- {
- updateSettings({ enableLCMLora: value })
- }}
- />
-
+ <>
+
+
+ {
+ updateSettings({ enableLCMLora: value })
+ }}
+ />
+
+
+ >
)
}
@@ -140,7 +156,7 @@ const SidePanel = () => {
return (
-
+
{
-
+
{
@@ -166,10 +185,13 @@ const SidePanel = () => {
/>
-
+
{
@@ -180,10 +202,13 @@ const SidePanel = () => {
/>
-
+
{
@@ -194,10 +219,13 @@ const SidePanel = () => {
/>
-
+
{
@@ -208,28 +236,51 @@ const SidePanel = () => {
/>
+
)
}
return (
-
+
-
event.preventDefault()}
onPointerDownOutside={(event) => event.preventDefault()}
>
- Diffusion Paramers
+
+
+ {
+ settings.model.name.split("/")[
+ settings.model.name.split("/").length - 1
+ ]
+ }
+
+
+
+
+
{
className="pr-3"
>
-
+
{
updateSettings({ showCroper: value })
}}
/>
-
+
-
+
{
updateSettings({ sdSteps: value })
}}
/>
-
+
-
+
{
updateSettings({ sdGuidanceScale: value })
}}
/>
-
+
-
-
-
{
- updateSettings({ sdStrength: value })
- }}
+
+
+
+
({settings.sdStrength})
+
+
+ updateSettings({ sdStrength: vals[0] / 100 })
+ }
/>
-
+
-
+
-
+
-
+
{/* 每次会从服务器返回更新该值 */}
@@ -336,24 +393,26 @@ const SidePanel = () => {
}}
/>
-
+
@@ -361,15 +420,10 @@ const SidePanel = () => {
{renderConterNetSetting()}
-
-
{renderFreeu()}
-
-
{renderLCMLora()}
-
-
+
{
updateSettings({ sdMaskBlur: value })
}}
/>
-
+
-
+
{
updateSettings({ sdMatchHistograms: value })
}}
/>
-
+
diff --git a/web_app/src/components/ui/button.tsx b/web_app/src/components/ui/button.tsx
index 009ad3d..b95fcce 100644
--- a/web_app/src/components/ui/button.tsx
+++ b/web_app/src/components/ui/button.tsx
@@ -52,8 +52,12 @@ const Button = React.forwardRef(
const Comp = asChild ? Slot : "button"
return (
)
diff --git a/web_app/src/components/ui/dialog.tsx b/web_app/src/components/ui/dialog.tsx
index 2817d93..af400ff 100644
--- a/web_app/src/components/ui/dialog.tsx
+++ b/web_app/src/components/ui/dialog.tsx
@@ -37,7 +37,8 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 flex flex-col w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
- className
+ className,
+ "outline-none"
)}
onCloseAutoFocus={(event) => event.preventDefault()}
{...props}
diff --git a/web_app/src/components/ui/dropdown-menu.tsx b/web_app/src/components/ui/dropdown-menu.tsx
index 1938c75..c85dcc0 100644
--- a/web_app/src/components/ui/dropdown-menu.tsx
+++ b/web_app/src/components/ui/dropdown-menu.tsx
@@ -71,8 +71,8 @@ const DropdownMenuContent = React.forwardRef<
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
- {...props}
onCloseAutoFocus={(e) => e.preventDefault()}
+ {...props}
/>
))
diff --git a/web_app/src/components/ui/input.tsx b/web_app/src/components/ui/input.tsx
index f72d89b..c88093c 100644
--- a/web_app/src/components/ui/input.tsx
+++ b/web_app/src/components/ui/input.tsx
@@ -1,12 +1,23 @@
import * as React from "react"
+import { FocusEvent } from "react"
import { cn } from "@/lib/utils"
+import { useStore } from "@/lib/states"
export interface InputProps
extends React.InputHTMLAttributes {}
const Input = React.forwardRef(
({ className, type, ...props }, ref) => {
+ const updateAppState = useStore((state) => state.updateAppState)
+
+ const handleOnFocus = (evt: FocusEvent) => {
+ updateAppState({ disableShortCuts: true })
+ }
+
+ const handleOnBlur = (evt: FocusEvent) => {
+ updateAppState({ disableShortCuts: false })
+ }
return (
(
)}
ref={ref}
autoComplete="off"
+ tabIndex={-1}
+ onFocus={handleOnFocus}
+ onBlur={handleOnBlur}
{...props}
/>
)
diff --git a/web_app/src/components/ui/label.tsx b/web_app/src/components/ui/label.tsx
index 683faa7..bf021cd 100644
--- a/web_app/src/components/ui/label.tsx
+++ b/web_app/src/components/ui/label.tsx
@@ -8,14 +8,19 @@ const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
+interface LabelProps {
+ disabled?: boolean
+}
+
const Label = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, ...props }, ref) => (
+ VariantProps &
+ LabelProps
+>(({ className, disabled, ...props }, ref) => (
))
diff --git a/web_app/src/components/ui/sheet.tsx b/web_app/src/components/ui/sheet.tsx
index 7df0d9b..b401cad 100644
--- a/web_app/src/components/ui/sheet.tsx
+++ b/web_app/src/components/ui/sheet.tsx
@@ -1,6 +1,5 @@
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
-import { Cross2Icon } from "@radix-ui/react-icons"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
@@ -29,7 +28,7 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-200 data-[state=open]:duration-300",
{
variants: {
side: {
@@ -63,10 +62,6 @@ const SheetContent = React.forwardRef<
{...props}
>
{children}
-
-
- Close
-
))
diff --git a/web_app/src/globals.css b/web_app/src/globals.css
index 15579b9..9e58043 100644
--- a/web_app/src/globals.css
+++ b/web_app/src/globals.css
@@ -2,7 +2,7 @@
@tailwind components;
@tailwind utilities;
-html { font-family: "Inter", "system-ui"; }
+html { font-family: "Inter", "system-ui"; overflow: hidden; }
@supports (font-variation-settings: normal) {
html { font-family: "Inter var", "system-ui"; }
diff --git a/web_app/src/hooks/useHotkey.tsx b/web_app/src/hooks/useHotkey.tsx
new file mode 100644
index 0000000..5badd57
--- /dev/null
+++ b/web_app/src/hooks/useHotkey.tsx
@@ -0,0 +1,11 @@
+import { useStore } from "@/lib/states"
+import { useHotkeys } from "react-hotkeys-hook"
+
+const useHotKey = (keys: string, callback: any, deps?: any[]) => {
+ const disableShortCuts = useStore((state) => state.disableShortCuts)
+
+ const ref = useHotkeys(keys, callback, { enabled: !disableShortCuts }, deps)
+ return ref
+}
+
+export default useHotKey
diff --git a/web_app/src/lib/const.ts b/web_app/src/lib/const.ts
index 971a6c0..8b0efdf 100644
--- a/web_app/src/lib/const.ts
+++ b/web_app/src/lib/const.ts
@@ -7,3 +7,7 @@ export const MODEL_TYPE_DIFFUSERS_SD_INPAINT = "diffusers_sd_inpaint"
export const MODEL_TYPE_DIFFUSERS_SDXL_INPAINT = "diffusers_sdxl_inpaint"
export const MODEL_TYPE_OTHER = "diffusers_other"
export const BRUSH_COLOR = "#ffcc00bb"
+
+export const PAINT_BY_EXAMPLE = "Fantasy-Studio/Paint-by-Example"
+export const INSTRUCT_PIX2PIX = "timbrooks/instruct-pix2pix"
+export const KANDINSKY_2_2 = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
diff --git a/web_app/src/lib/states.ts b/web_app/src/lib/states.ts
index aa0e425..e144117 100644
--- a/web_app/src/lib/states.ts
+++ b/web_app/src/lib/states.ts
@@ -18,7 +18,13 @@ import {
SortBy,
SortOrder,
} from "./types"
-import { DEFAULT_BRUSH_SIZE, MODEL_TYPE_INPAINT } from "./const"
+import {
+ DEFAULT_BRUSH_SIZE,
+ INSTRUCT_PIX2PIX,
+ MODEL_TYPE_INPAINT,
+ MODEL_TYPE_OTHER,
+ PAINT_BY_EXAMPLE,
+} from "./const"
import { dataURItoBlob, generateMask, loadImage, srcToFile } from "./utils"
import inpaint, { runPlugin } from "./api"
import { toast, useToast } from "@/components/ui/use-toast"
@@ -84,6 +90,7 @@ export type Settings = {
p2pGuidanceScale: number
// ControlNet
+ enableControlNet: boolean
controlnetConditioningScale: number
controlnetMethod: string
@@ -122,8 +129,6 @@ type EditorState = {
}
type AppState = {
- idForUpdateView: string
-
file: File | null
customMask: File | null
imageHeight: number
@@ -132,6 +137,7 @@ type AppState = {
isPluginRunning: boolean
windowSize: Size
editorState: EditorState
+ disableShortCuts: boolean
interactiveSegState: InteractiveSegState
fileManagerState: FileManagerState
@@ -188,14 +194,13 @@ type AppAction = {
}
const defaultValues: AppState = {
- idForUpdateView: nanoid(),
-
file: null,
customMask: null,
imageHeight: 0,
imageWidth: 0,
isInpainting: false,
isPluginRunning: false,
+ disableShortCuts: false,
windowSize: {
height: 600,
@@ -254,6 +259,7 @@ const defaultValues: AppState = {
is_single_file_diffusers: false,
need_prompt: false,
},
+ enableControlNet: false,
showCroper: false,
enableDownloadMask: false,
enableManualInpainting: false,
@@ -311,7 +317,17 @@ export const useStore = createWithEqualityFn()(
},
runInpainting: async () => {
- const { file, imageWidth, imageHeight, settings, cropperState } = get()
+ const {
+ isInpainting,
+ file,
+ imageWidth,
+ imageHeight,
+ settings,
+ cropperState,
+ } = get()
+ if (isInpainting) {
+ return
+ }
if (file === null) {
return
@@ -656,13 +672,16 @@ export const useStore = createWithEqualityFn()(
},
showPromptInput: (): boolean => {
- const model_type = get().settings.model.model_type
- return ["diffusers_sd", "diffusers_sd_inpaint"].includes(model_type)
+ const model = get().settings.model
+ return (
+ model.model_type !== MODEL_TYPE_INPAINT &&
+ model.name !== PAINT_BY_EXAMPLE
+ )
},
showSidePanel: (): boolean => {
- const model_type = get().settings.model.model_type
- return ["diffusers_sd", "diffusers_sd_inpaint"].includes(model_type)
+ const model = get().settings.model
+ return model.model_type !== MODEL_TYPE_INPAINT
},
setServerConfig: (newValue: ServerConfig) => {