This commit is contained in:
Qing 2023-12-13 22:56:09 +08:00
parent 354a1280a4
commit 142aa64cc6
19 changed files with 360 additions and 181 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

View File

@ -0,0 +1,42 @@
import { Coffee as CoffeeIcon } from "lucide-react"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./ui/dialog"
import { IconButton } from "./ui/button"
import { DialogDescription } from "@radix-ui/react-dialog"
import Kofi from "@/assets/kofi_button_black.png"
export function Coffee() {
return (
<Dialog>
<DialogTrigger asChild>
<IconButton tooltip="Buy me a coffee">
<CoffeeIcon />
</IconButton>
</DialogTrigger>
<DialogContent>
<DialogTitle>Buy me a coffee</DialogTitle>
<DialogDescription className="mb-8">
Hi, if you found my project is useful, please conside buy me a coffee
to support my work. Thanks!
</DialogDescription>
<div className="w-full flex items-center justify-center">
<a
href="https://ko-fi.com/Z8Z1CZJGY"
target="_blank"
rel="noreferrer"
>
<img src={Kofi} className="h-[32px]" />
</a>
</div>
</DialogContent>
</Dialog>
)
}
export default Coffee

View File

@ -17,7 +17,6 @@ import {
generateMask,
isMidClick,
isRightClick,
loadImage,
mouseXY,
srcToFile,
} from "@/lib/utils"
@ -25,10 +24,10 @@ import { Eraser, Eye, Redo, Undo, Expand, Download } from "lucide-react"
import { useImage } from "@/hooks/useImage"
import { Slider } from "./ui/slider"
import { PluginName } from "@/lib/types"
import { useHotkeys } from "react-hotkeys-hook"
import { useStore } from "@/lib/states"
import Cropper from "./Cropper"
import { InteractiveSegPoints } from "./InteractiveSeg"
import useHotKey from "@/hooks/useHotkey"
const TOOLBAR_HEIGHT = 200
const MIN_BRUSH_SIZE = 10
@ -44,7 +43,7 @@ export default function Editor(props: EditorProps) {
const { toast } = useToast()
const [
idForUpdateView,
disableShortCuts,
windowSize,
isInpainting,
imageWidth,
@ -76,7 +75,7 @@ export default function Editor(props: EditorProps) {
runMannually,
runInpainting,
] = useStore((state) => [
state.idForUpdateView,
state.disableShortCuts,
state.windowSize,
state.isInpainting,
state.imageWidth,
@ -346,7 +345,7 @@ export default function Editor(props: EditorProps) {
}
}
useHotkeys("Escape", handleEscPressed, [
useHotKey("Escape", handleEscPressed, [
isDraging,
isInpainting,
resetZoom,
@ -509,13 +508,13 @@ export default function Editor(props: EditorProps) {
keyboardEvent.preventDefault()
undo()
}
useHotkeys("meta+z,ctrl+z", handleUndo)
useHotKey("meta+z,ctrl+z", handleUndo)
const handleRedo = (keyboardEvent: KeyboardEvent | SyntheticEvent) => {
keyboardEvent.preventDefault()
redo()
}
useHotkeys("shift+ctrl+z,shift+meta+z", handleRedo)
useHotKey("shift+ctrl+z,shift+meta+z", handleRedo)
useKeyPressEvent(
"Tab",
@ -601,7 +600,7 @@ export default function Editor(props: EditorProps) {
return undefined
}, [showBrush, isPanning])
useHotkeys(
useHotKey(
"[",
() => {
let newBrushSize = baseBrushSize
@ -616,7 +615,7 @@ export default function Editor(props: EditorProps) {
[baseBrushSize]
)
useHotkeys(
useHotKey(
"]",
() => {
setBaseBrushSize(baseBrushSize + 10)
@ -625,7 +624,7 @@ export default function Editor(props: EditorProps) {
)
// Manual Inpainting Hotkey
useHotkeys(
useHotKey(
"shift+r",
() => {
if (runMannually && hadDrawSomething()) {
@ -635,7 +634,7 @@ export default function Editor(props: EditorProps) {
[runMannually, runInpainting, hadDrawSomething]
)
useHotkeys(
useHotKey(
"ctrl+c, cmd+c",
async () => {
const hasPermission = await askWritePermission()
@ -655,17 +654,21 @@ export default function Editor(props: EditorProps) {
useKeyPressEvent(
" ",
(ev) => {
if (!disableShortCuts) {
ev?.preventDefault()
ev?.stopPropagation()
setShowBrush(false)
setIsPanning(true)
}
},
(ev) => {
if (!disableShortCuts) {
ev?.preventDefault()
ev?.stopPropagation()
setShowBrush(true)
setIsPanning(false)
}
}
)
useKeyPressEvent(
@ -738,7 +741,6 @@ export default function Editor(props: EditorProps) {
const renderCanvas = () => {
return (
<TransformWrapper
// ref={viewportRef}
ref={(r) => {
if (r) {
viewportRef.current = r
@ -865,7 +867,6 @@ export default function Editor(props: EditorProps) {
onMouseUp={onPointerUp}
>
{renderCanvas()}
{showBrush &&
!isInpainting &&
!isPanning &&
@ -875,7 +876,7 @@ export default function Editor(props: EditorProps) {
{showRefBrush && renderBrush(getBrushStyle(windowCenterX, windowCenterY))}
<div className="fixed flex bottom-5 border px-4 py-2 rounded-[3rem] gap-8 items-center justify-center backdrop-filter backdrop-blur-md bg-background/70">
<div className=" overflow-hidden fixed flex bottom-5 border px-4 py-2 rounded-[3rem] gap-8 items-center justify-center backdrop-filter backdrop-blur-md bg-background/70">
<Slider
className="w-48"
defaultValue={[50]}

View File

@ -32,10 +32,10 @@ import {
} from "./ui/select"
import { ScrollArea } from "./ui/scroll-area"
import { DialogTrigger } from "@radix-ui/react-dialog"
import { useHotkeys } from "react-hotkeys-hook"
import { useStore } from "@/lib/states"
import { SortBy, SortOrder } from "@/lib/types"
import { FolderClosed } from "lucide-react"
import useHotKey from "@/hooks/useHotkey"
interface Photo {
src: string
@ -79,7 +79,7 @@ export default function FileManager(props: Props) {
state.updateFileManagerState,
])
useHotkeys("f", () => {
useHotKey("f", () => {
toggleOpen()
})

View File

@ -1,6 +1,5 @@
import { PlayIcon } from "@radix-ui/react-icons"
import { useCallback, useState } from "react"
import { useHotkeys } from "react-hotkeys-hook"
import { IconButton, ImageUploadButton } from "@/components/ui/button"
import Shortcuts from "@/components/Shortcuts"
import emitter, {
@ -19,6 +18,8 @@ import { getMediaFile } from "@/lib/api"
import { useStore } from "@/lib/states"
import SettingsDialog from "./Settings"
import { cn } from "@/lib/utils"
import useHotKey from "@/hooks/useHotkey"
import Coffee from "./Coffee"
const Header = () => {
const [
@ -57,7 +58,7 @@ const Header = () => {
emitter.emit(DREAM_BUTTON_MOUSE_LEAVE)
}
useHotkeys(
useHotKey(
"r",
() => {
if (!isInpainting) {
@ -163,7 +164,7 @@ const Header = () => {
{model.need_prompt ? <PromptInput /> : <></>}
<div className="flex gap-1">
{/* <CoffeeIcon /> */}
<Coffee />
<Shortcuts />
<SettingsDialog />
</div>

View File

@ -1,7 +1,8 @@
import React, { FormEvent } from "react"
import React, { FormEvent, useRef } from "react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { useStore } from "@/lib/states"
import { useClickAway } from "react-use"
const PromptInput = () => {
const [isProcessing, prompt, updateSettings, runInpainting] = useStore(
@ -12,6 +13,14 @@ const PromptInput = () => {
state.runInpainting,
]
)
const ref = useRef(null)
useClickAway<MouseEvent>(ref, () => {
if (ref?.current) {
const input = ref.current as HTMLInputElement
input.blur()
}
})
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
evt.preventDefault()
@ -43,6 +52,7 @@ const PromptInput = () => {
return (
<div className="flex gap-4 items-center">
<Input
ref={ref}
className="min-w-[500px]"
value={prompt}
onInput={handleOnInput}

View File

@ -1,7 +1,6 @@
import { IconButton } from "@/components/ui/button"
import { useToggle } from "@uidotdev/usehooks"
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog"
import { useHotkeys } from "react-hotkeys-hook"
import { Info, Settings } from "lucide-react"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
@ -20,7 +19,7 @@ import {
import { Input } from "@/components/ui/input"
import { Switch } from "./ui/switch"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
import { useEffect, useState } from "react"
import { useState } from "react"
import { cn } from "@/lib/utils"
import { useQuery } from "@tanstack/react-query"
import { fetchModelInfos, switchModel } from "@/lib/api"
@ -42,6 +41,7 @@ import {
MODEL_TYPE_INPAINT,
MODEL_TYPE_OTHER,
} from "@/lib/const"
import useHotKey from "@/hooks/useHotkey"
const formSchema = z.object({
enableFileManager: z.boolean(),
@ -68,8 +68,14 @@ export function SettingsDialog() {
const [open, toggleOpen] = useToggle(false)
const [openModelSwitching, toggleOpenModelSwitching] = useToggle(false)
const [tab, setTab] = useState(TAB_MODEL)
const [settings, updateSettings, fileManagerState, updateFileManagerState] =
useStore((state) => [
const [
updateAppState,
settings,
updateSettings,
fileManagerState,
updateFileManagerState,
] = useStore((state) => [
state.updateAppState,
state.settings,
state.updateSettings,
state.fileManagerState,
@ -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() {
<AlertDialog open={openModelSwitching}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogDescription>
TODO: 添加加载动画 Switching to {model.name}
</AlertDialogDescription>
{/* <AlertDialogDescription> */}
<div className="flex flex-col justify-center items-center gap-4">
<div>logo</div>
<div>Switching to {model.name}</div>
</div>
{/* </AlertDialogDescription> */}
</AlertDialogHeader>
</AlertDialogContent>
</AlertDialog>
@ -434,7 +452,7 @@ export function SettingsDialog() {
)} */}
<div className="absolute right-10 bottom-6">
<Button onClick={() => toggleOpen()}>Ok</Button>
<Button onClick={() => onOpenChange(false)}>Ok</Button>
</div>
</form>
</div>

View File

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

View File

@ -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 }) => (
<div className="flex justify-between items-center pr-2">{children}</div>
)
const SidePanel = () => {
const [settings, updateSettings, showSidePanel] = useStore((state) => [
const [settings, updateSettings, showSidePanel, runInpainting] = useStore(
(state) => [
state.settings,
state.updateSettings,
state.showSidePanel(),
])
const [open, toggleOpen] = useToggle(true)
const [expandedAccordionItems, setExpandedAccordionItems] = useState<
string[]
>([])
state.runInpainting,
]
)
const [open, toggleOpen] = useToggle(false)
useHotkeys("c", () => {
useHotKey("c", () => {
toggleOpen()
})
@ -58,13 +55,8 @@ const SidePanel = () => {
const onKeyUp = (e: React.KeyboardEvent) => {
// negativePrompt 回车触发 inpainting
if (
e.key === "Enter" &&
e.ctrlKey &&
settings.prompt.length !== 0
// !isInpainting
) {
console.log("trigger negativePrompt")
if (e.key === "Enter" && e.ctrlKey && settings.prompt.length !== 0) {
runInpainting()
}
}
@ -75,15 +67,27 @@ const SidePanel = () => {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col items-start gap-4">
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center pr-2">
<Label htmlFor="controlnet">Controlnet</Label>
<Switch
id="controlnet"
checked={settings.enableControlNet}
onCheckedChange={(value) => {
updateSettings({ enableControlNet: value })
}}
/>
</div>
<div className="pl-1 pr-2">
<Select
value={settings.controlnetMethod}
onValueChange={(value) => {
updateSettings({ controlnetMethod: value })
}}
disabled={!settings.enableControlNet}
>
<SelectTrigger id="controlnet">
<SelectTrigger>
<SelectValue placeholder="Select control method" />
</SelectTrigger>
<SelectContent align="end">
@ -97,19 +101,28 @@ const SidePanel = () => {
</SelectContent>
</Select>
</div>
</div>
<div className="flex justify-between items-center">
<Label htmlFor="controlnet-weight">weight</Label>
<RowContainer>
<Label
htmlFor="controlnet-weight"
disabled={!settings.enableControlNet}
>
weight
</Label>
<NumberInput
id="controlnet-weight"
className="w-14"
disabled={!settings.enableControlNet}
numberValue={settings.controlnetConditioningScale}
allowFloat
onNumberValueChange={(value) => {
updateSettings({ controlnetConditioningScale: value })
}}
/>
</div>
</RowContainer>
<Separator />
</div>
)
}
@ -120,7 +133,8 @@ const SidePanel = () => {
}
return (
<div className="flex justify-between items-center">
<>
<RowContainer>
<Label htmlFor="lcm-lora">LCM Lora</Label>
<Switch
id="lcm-lora"
@ -129,7 +143,9 @@ const SidePanel = () => {
updateSettings({ enableLCMLora: value })
}}
/>
</div>
</RowContainer>
<Separator />
</>
)
}
@ -140,7 +156,7 @@ const SidePanel = () => {
return (
<div className="flex flex-col gap-4">
<div className="flex justify-between items-center">
<div className="flex justify-between items-center pr-2">
<Label htmlFor="freeu">Freeu</Label>
<Switch
id="freeu"
@ -152,10 +168,13 @@ const SidePanel = () => {
</div>
<div className="flex gap-3">
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-s1">s1</Label>
<Label htmlFor="freeu-s1" disabled={!settings.enableFreeu}>
s1
</Label>
<NumberInput
id="freeu-s1"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.s1}
allowFloat
onNumberValueChange={(value) => {
@ -166,10 +185,13 @@ const SidePanel = () => {
/>
</div>
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-s2">s2</Label>
<Label htmlFor="freeu-s2" disabled={!settings.enableFreeu}>
s2
</Label>
<NumberInput
id="freeu-s2"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.s2}
allowFloat
onNumberValueChange={(value) => {
@ -180,10 +202,13 @@ const SidePanel = () => {
/>
</div>
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-b1">b1</Label>
<Label htmlFor="freeu-b1" disabled={!settings.enableFreeu}>
b1
</Label>
<NumberInput
id="freeu-b1"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.b1}
allowFloat
onNumberValueChange={(value) => {
@ -194,10 +219,13 @@ const SidePanel = () => {
/>
</div>
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-b2">b2</Label>
<Label htmlFor="freeu-b2" disabled={!settings.enableFreeu}>
b2
</Label>
<NumberInput
id="freeu-b2"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.b2}
allowFloat
onNumberValueChange={(value) => {
@ -208,28 +236,51 @@ const SidePanel = () => {
/>
</div>
</div>
<Separator />
</div>
)
}
return (
<Sheet open={open} onOpenChange={toggleOpen} modal={false}>
<Sheet open={open} modal={false}>
<SheetTrigger
tabIndex={-1}
className="z-10 outline-none absolute top-[68px] right-6 rounded-lg border bg-background"
>
<Button variant="ghost" size="icon" asChild className="p-1.5">
<Button
variant="ghost"
size="icon"
asChild
className="p-1.5"
onClick={toggleOpen}
>
<ChevronLeft strokeWidth={1} />
</Button>
</SheetTrigger>
<SheetContent
side="right"
className="w-[300px] mt-[60px] outline-none pl-4 pr-1"
className="w-[300px] mt-[60px] outline-none pl-4 pr-1 backdrop-filter backdrop-blur-md bg-background/70"
onOpenAutoFocus={(event) => event.preventDefault()}
onPointerDownOutside={(event) => event.preventDefault()}
>
<SheetHeader className="mb-4">
<SheetTitle>Diffusion Paramers</SheetTitle>
<RowContainer>
<div className="overflow-hidden mr-8">
{
settings.model.name.split("/")[
settings.model.name.split("/").length - 1
]
}
</div>
<Button
variant="ghost"
size="icon"
className="border h-6 w-6"
onClick={toggleOpen}
>
<ChevronRight strokeWidth={1} />
</Button>
</RowContainer>
<Separator />
</SheetHeader>
<ScrollArea
@ -237,7 +288,7 @@ const SidePanel = () => {
className="pr-3"
>
<div className="flex flex-col gap-3">
<div className="flex justify-between items-center">
<RowContainer>
<Label htmlFor="cropper">Cropper</Label>
<Switch
id="cropper"
@ -246,9 +297,9 @@ const SidePanel = () => {
updateSettings({ showCroper: value })
}}
/>
</div>
</RowContainer>
<div className="flex justify-between items-center">
<RowContainer>
<Label htmlFor="steps">Steps</Label>
<NumberInput
id="steps"
@ -259,9 +310,9 @@ const SidePanel = () => {
updateSettings({ sdSteps: value })
}}
/>
</div>
</RowContainer>
<div className="flex justify-between items-center">
<RowContainer>
<Label htmlFor="guidance-scale">Guidance scale</Label>
<NumberInput
id="guidance-scale"
@ -272,22 +323,28 @@ const SidePanel = () => {
updateSettings({ sdGuidanceScale: value })
}}
/>
</div>
</RowContainer>
<div className="flex justify-between items-center">
<RowContainer>
<div className="flex gap-2 items-center">
<Label htmlFor="strength">Strength</Label>
<NumberInput
id="strength"
className="w-14"
numberValue={settings.sdStrength}
allowFloat
onNumberValueChange={(value) => {
updateSettings({ sdStrength: value })
}}
/>
<div className="text-sm">({settings.sdStrength})</div>
</div>
<Slider
className="w-24"
defaultValue={[100]}
min={10}
max={100}
step={1}
tabIndex={-1}
value={[Math.floor(settings.sdStrength * 100)]}
onValueChange={(vals) =>
updateSettings({ sdStrength: vals[0] / 100 })
}
/>
</RowContainer>
<div className="flex justify-between items-center">
<RowContainer>
<Label htmlFor="sampler">Sampler</Label>
<Select
value={settings.sdSampler as string}
@ -312,9 +369,9 @@ const SidePanel = () => {
</SelectGroup>
</SelectContent>
</Select>
</div>
</RowContainer>
<div className="flex justify-between items-center">
<RowContainer>
{/* 每次会从服务器返回更新该值 */}
<Label htmlFor="seed">Seed</Label>
<div className="flex gap-2 justify-center items-center">
@ -336,10 +393,11 @@ const SidePanel = () => {
}}
/>
</div>
</div>
</RowContainer>
<div className="flex flex-col gap-4">
<Label htmlFor="negative-prompt">Negative prompt</Label>
<div className="pl-2 pr-4">
<Textarea
rows={4}
onKeyUp={onKeyUp}
@ -355,21 +413,17 @@ const SidePanel = () => {
}}
/>
</div>
</div>
<Separator />
<div className="flex flex-col gap-4">
{renderConterNetSetting()}
</div>
<Separator />
{renderFreeu()}
<Separator />
{renderLCMLora()}
<Separator />
<div className="flex justify-between items-center">
<RowContainer>
<Label htmlFor="mask-blur">Mask blur</Label>
<NumberInput
id="mask-blur"
@ -380,9 +434,9 @@ const SidePanel = () => {
updateSettings({ sdMaskBlur: value })
}}
/>
</div>
</RowContainer>
<div className="flex justify-between items-center">
<RowContainer>
<Label htmlFor="match-histograms">Match histograms</Label>
<Switch
id="match-histograms"
@ -391,7 +445,7 @@ const SidePanel = () => {
updateSettings({ sdMatchHistograms: value })
}}
/>
</div>
</RowContainer>
</div>
</ScrollArea>
</SheetContent>

View File

@ -52,8 +52,12 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
className={cn(
buttonVariants({ variant, size, className }),
"outline-none cursor-default"
)}
ref={ref}
tabIndex={-1}
{...props}
/>
)

View File

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

View File

@ -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}
/>
</DropdownMenuPrimitive.Portal>
))

View File

@ -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<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
const updateAppState = useStore((state) => state.updateAppState)
const handleOnFocus = (evt: FocusEvent<any>) => {
updateAppState({ disableShortCuts: true })
}
const handleOnBlur = (evt: FocusEvent<any>) => {
updateAppState({ disableShortCuts: false })
}
return (
<input
type={type}
@ -16,6 +27,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
)}
ref={ref}
autoComplete="off"
tabIndex={-1}
onFocus={handleOnFocus}
onBlur={handleOnBlur}
{...props}
/>
)

View File

@ -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<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
VariantProps<typeof labelVariants> &
LabelProps
>(({ className, disabled, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
className={cn(labelVariants(), className, disabled ? "opacity-50" : "")}
{...props}
/>
))

View File

@ -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}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))

View File

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

View File

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

View File

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

View File

@ -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<AppState & AppAction>()(
},
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<AppState & AppAction>()(
},
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) => {