update
This commit is contained in:
parent
354a1280a4
commit
142aa64cc6
BIN
web_app/src/assets/kofi_button_black.png
Normal file
BIN
web_app/src/assets/kofi_button_black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 203 KiB |
42
web_app/src/components/Coffee.tsx
Normal file
42
web_app/src/components/Coffee.tsx
Normal 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
|
@ -17,7 +17,6 @@ import {
|
|||||||
generateMask,
|
generateMask,
|
||||||
isMidClick,
|
isMidClick,
|
||||||
isRightClick,
|
isRightClick,
|
||||||
loadImage,
|
|
||||||
mouseXY,
|
mouseXY,
|
||||||
srcToFile,
|
srcToFile,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
@ -25,10 +24,10 @@ import { Eraser, Eye, Redo, Undo, Expand, Download } from "lucide-react"
|
|||||||
import { useImage } from "@/hooks/useImage"
|
import { useImage } from "@/hooks/useImage"
|
||||||
import { Slider } from "./ui/slider"
|
import { Slider } from "./ui/slider"
|
||||||
import { PluginName } from "@/lib/types"
|
import { PluginName } from "@/lib/types"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
import Cropper from "./Cropper"
|
import Cropper from "./Cropper"
|
||||||
import { InteractiveSegPoints } from "./InteractiveSeg"
|
import { InteractiveSegPoints } from "./InteractiveSeg"
|
||||||
|
import useHotKey from "@/hooks/useHotkey"
|
||||||
|
|
||||||
const TOOLBAR_HEIGHT = 200
|
const TOOLBAR_HEIGHT = 200
|
||||||
const MIN_BRUSH_SIZE = 10
|
const MIN_BRUSH_SIZE = 10
|
||||||
@ -44,7 +43,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
const [
|
const [
|
||||||
idForUpdateView,
|
disableShortCuts,
|
||||||
windowSize,
|
windowSize,
|
||||||
isInpainting,
|
isInpainting,
|
||||||
imageWidth,
|
imageWidth,
|
||||||
@ -76,7 +75,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
runMannually,
|
runMannually,
|
||||||
runInpainting,
|
runInpainting,
|
||||||
] = useStore((state) => [
|
] = useStore((state) => [
|
||||||
state.idForUpdateView,
|
state.disableShortCuts,
|
||||||
state.windowSize,
|
state.windowSize,
|
||||||
state.isInpainting,
|
state.isInpainting,
|
||||||
state.imageWidth,
|
state.imageWidth,
|
||||||
@ -346,7 +345,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useHotkeys("Escape", handleEscPressed, [
|
useHotKey("Escape", handleEscPressed, [
|
||||||
isDraging,
|
isDraging,
|
||||||
isInpainting,
|
isInpainting,
|
||||||
resetZoom,
|
resetZoom,
|
||||||
@ -509,13 +508,13 @@ export default function Editor(props: EditorProps) {
|
|||||||
keyboardEvent.preventDefault()
|
keyboardEvent.preventDefault()
|
||||||
undo()
|
undo()
|
||||||
}
|
}
|
||||||
useHotkeys("meta+z,ctrl+z", handleUndo)
|
useHotKey("meta+z,ctrl+z", handleUndo)
|
||||||
|
|
||||||
const handleRedo = (keyboardEvent: KeyboardEvent | SyntheticEvent) => {
|
const handleRedo = (keyboardEvent: KeyboardEvent | SyntheticEvent) => {
|
||||||
keyboardEvent.preventDefault()
|
keyboardEvent.preventDefault()
|
||||||
redo()
|
redo()
|
||||||
}
|
}
|
||||||
useHotkeys("shift+ctrl+z,shift+meta+z", handleRedo)
|
useHotKey("shift+ctrl+z,shift+meta+z", handleRedo)
|
||||||
|
|
||||||
useKeyPressEvent(
|
useKeyPressEvent(
|
||||||
"Tab",
|
"Tab",
|
||||||
@ -601,7 +600,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
return undefined
|
return undefined
|
||||||
}, [showBrush, isPanning])
|
}, [showBrush, isPanning])
|
||||||
|
|
||||||
useHotkeys(
|
useHotKey(
|
||||||
"[",
|
"[",
|
||||||
() => {
|
() => {
|
||||||
let newBrushSize = baseBrushSize
|
let newBrushSize = baseBrushSize
|
||||||
@ -616,7 +615,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
[baseBrushSize]
|
[baseBrushSize]
|
||||||
)
|
)
|
||||||
|
|
||||||
useHotkeys(
|
useHotKey(
|
||||||
"]",
|
"]",
|
||||||
() => {
|
() => {
|
||||||
setBaseBrushSize(baseBrushSize + 10)
|
setBaseBrushSize(baseBrushSize + 10)
|
||||||
@ -625,7 +624,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Manual Inpainting Hotkey
|
// Manual Inpainting Hotkey
|
||||||
useHotkeys(
|
useHotKey(
|
||||||
"shift+r",
|
"shift+r",
|
||||||
() => {
|
() => {
|
||||||
if (runMannually && hadDrawSomething()) {
|
if (runMannually && hadDrawSomething()) {
|
||||||
@ -635,7 +634,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
[runMannually, runInpainting, hadDrawSomething]
|
[runMannually, runInpainting, hadDrawSomething]
|
||||||
)
|
)
|
||||||
|
|
||||||
useHotkeys(
|
useHotKey(
|
||||||
"ctrl+c, cmd+c",
|
"ctrl+c, cmd+c",
|
||||||
async () => {
|
async () => {
|
||||||
const hasPermission = await askWritePermission()
|
const hasPermission = await askWritePermission()
|
||||||
@ -655,16 +654,20 @@ export default function Editor(props: EditorProps) {
|
|||||||
useKeyPressEvent(
|
useKeyPressEvent(
|
||||||
" ",
|
" ",
|
||||||
(ev) => {
|
(ev) => {
|
||||||
ev?.preventDefault()
|
if (!disableShortCuts) {
|
||||||
ev?.stopPropagation()
|
ev?.preventDefault()
|
||||||
setShowBrush(false)
|
ev?.stopPropagation()
|
||||||
setIsPanning(true)
|
setShowBrush(false)
|
||||||
|
setIsPanning(true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(ev) => {
|
(ev) => {
|
||||||
ev?.preventDefault()
|
if (!disableShortCuts) {
|
||||||
ev?.stopPropagation()
|
ev?.preventDefault()
|
||||||
setShowBrush(true)
|
ev?.stopPropagation()
|
||||||
setIsPanning(false)
|
setShowBrush(true)
|
||||||
|
setIsPanning(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -738,7 +741,6 @@ export default function Editor(props: EditorProps) {
|
|||||||
const renderCanvas = () => {
|
const renderCanvas = () => {
|
||||||
return (
|
return (
|
||||||
<TransformWrapper
|
<TransformWrapper
|
||||||
// ref={viewportRef}
|
|
||||||
ref={(r) => {
|
ref={(r) => {
|
||||||
if (r) {
|
if (r) {
|
||||||
viewportRef.current = r
|
viewportRef.current = r
|
||||||
@ -865,7 +867,6 @@ export default function Editor(props: EditorProps) {
|
|||||||
onMouseUp={onPointerUp}
|
onMouseUp={onPointerUp}
|
||||||
>
|
>
|
||||||
{renderCanvas()}
|
{renderCanvas()}
|
||||||
|
|
||||||
{showBrush &&
|
{showBrush &&
|
||||||
!isInpainting &&
|
!isInpainting &&
|
||||||
!isPanning &&
|
!isPanning &&
|
||||||
@ -875,7 +876,7 @@ export default function Editor(props: EditorProps) {
|
|||||||
|
|
||||||
{showRefBrush && renderBrush(getBrushStyle(windowCenterX, windowCenterY))}
|
{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
|
<Slider
|
||||||
className="w-48"
|
className="w-48"
|
||||||
defaultValue={[50]}
|
defaultValue={[50]}
|
||||||
|
@ -32,10 +32,10 @@ import {
|
|||||||
} from "./ui/select"
|
} from "./ui/select"
|
||||||
import { ScrollArea } from "./ui/scroll-area"
|
import { ScrollArea } from "./ui/scroll-area"
|
||||||
import { DialogTrigger } from "@radix-ui/react-dialog"
|
import { DialogTrigger } from "@radix-ui/react-dialog"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
import { SortBy, SortOrder } from "@/lib/types"
|
import { SortBy, SortOrder } from "@/lib/types"
|
||||||
import { FolderClosed } from "lucide-react"
|
import { FolderClosed } from "lucide-react"
|
||||||
|
import useHotKey from "@/hooks/useHotkey"
|
||||||
|
|
||||||
interface Photo {
|
interface Photo {
|
||||||
src: string
|
src: string
|
||||||
@ -79,7 +79,7 @@ export default function FileManager(props: Props) {
|
|||||||
state.updateFileManagerState,
|
state.updateFileManagerState,
|
||||||
])
|
])
|
||||||
|
|
||||||
useHotkeys("f", () => {
|
useHotKey("f", () => {
|
||||||
toggleOpen()
|
toggleOpen()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { PlayIcon } from "@radix-ui/react-icons"
|
import { PlayIcon } from "@radix-ui/react-icons"
|
||||||
import { useCallback, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
|
||||||
import { IconButton, ImageUploadButton } from "@/components/ui/button"
|
import { IconButton, ImageUploadButton } from "@/components/ui/button"
|
||||||
import Shortcuts from "@/components/Shortcuts"
|
import Shortcuts from "@/components/Shortcuts"
|
||||||
import emitter, {
|
import emitter, {
|
||||||
@ -19,6 +18,8 @@ import { getMediaFile } from "@/lib/api"
|
|||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
import SettingsDialog from "./Settings"
|
import SettingsDialog from "./Settings"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import useHotKey from "@/hooks/useHotkey"
|
||||||
|
import Coffee from "./Coffee"
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const [
|
const [
|
||||||
@ -57,7 +58,7 @@ const Header = () => {
|
|||||||
emitter.emit(DREAM_BUTTON_MOUSE_LEAVE)
|
emitter.emit(DREAM_BUTTON_MOUSE_LEAVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
useHotkeys(
|
useHotKey(
|
||||||
"r",
|
"r",
|
||||||
() => {
|
() => {
|
||||||
if (!isInpainting) {
|
if (!isInpainting) {
|
||||||
@ -163,7 +164,7 @@ const Header = () => {
|
|||||||
{model.need_prompt ? <PromptInput /> : <></>}
|
{model.need_prompt ? <PromptInput /> : <></>}
|
||||||
|
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{/* <CoffeeIcon /> */}
|
<Coffee />
|
||||||
<Shortcuts />
|
<Shortcuts />
|
||||||
<SettingsDialog />
|
<SettingsDialog />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React, { FormEvent } from "react"
|
import React, { FormEvent, useRef } from "react"
|
||||||
import { Button } from "./ui/button"
|
import { Button } from "./ui/button"
|
||||||
import { Input } from "./ui/input"
|
import { Input } from "./ui/input"
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
|
import { useClickAway } from "react-use"
|
||||||
|
|
||||||
const PromptInput = () => {
|
const PromptInput = () => {
|
||||||
const [isProcessing, prompt, updateSettings, runInpainting] = useStore(
|
const [isProcessing, prompt, updateSettings, runInpainting] = useStore(
|
||||||
@ -12,6 +13,14 @@ const PromptInput = () => {
|
|||||||
state.runInpainting,
|
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>) => {
|
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
@ -43,6 +52,7 @@ const PromptInput = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
<Input
|
<Input
|
||||||
|
ref={ref}
|
||||||
className="min-w-[500px]"
|
className="min-w-[500px]"
|
||||||
value={prompt}
|
value={prompt}
|
||||||
onInput={handleOnInput}
|
onInput={handleOnInput}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { IconButton } from "@/components/ui/button"
|
import { IconButton } from "@/components/ui/button"
|
||||||
import { useToggle } from "@uidotdev/usehooks"
|
import { useToggle } from "@uidotdev/usehooks"
|
||||||
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog"
|
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
|
||||||
import { Info, Settings } from "lucide-react"
|
import { Info, Settings } from "lucide-react"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
@ -20,7 +19,7 @@ import {
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Switch } from "./ui/switch"
|
import { Switch } from "./ui/switch"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
||||||
import { useEffect, useState } from "react"
|
import { useState } from "react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { fetchModelInfos, switchModel } from "@/lib/api"
|
import { fetchModelInfos, switchModel } from "@/lib/api"
|
||||||
@ -42,6 +41,7 @@ import {
|
|||||||
MODEL_TYPE_INPAINT,
|
MODEL_TYPE_INPAINT,
|
||||||
MODEL_TYPE_OTHER,
|
MODEL_TYPE_OTHER,
|
||||||
} from "@/lib/const"
|
} from "@/lib/const"
|
||||||
|
import useHotKey from "@/hooks/useHotkey"
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
enableFileManager: z.boolean(),
|
enableFileManager: z.boolean(),
|
||||||
@ -68,13 +68,19 @@ export function SettingsDialog() {
|
|||||||
const [open, toggleOpen] = useToggle(false)
|
const [open, toggleOpen] = useToggle(false)
|
||||||
const [openModelSwitching, toggleOpenModelSwitching] = useToggle(false)
|
const [openModelSwitching, toggleOpenModelSwitching] = useToggle(false)
|
||||||
const [tab, setTab] = useState(TAB_MODEL)
|
const [tab, setTab] = useState(TAB_MODEL)
|
||||||
const [settings, updateSettings, fileManagerState, updateFileManagerState] =
|
const [
|
||||||
useStore((state) => [
|
updateAppState,
|
||||||
state.settings,
|
settings,
|
||||||
state.updateSettings,
|
updateSettings,
|
||||||
state.fileManagerState,
|
fileManagerState,
|
||||||
state.updateFileManagerState,
|
updateFileManagerState,
|
||||||
])
|
] = useStore((state) => [
|
||||||
|
state.updateAppState,
|
||||||
|
state.settings,
|
||||||
|
state.updateSettings,
|
||||||
|
state.fileManagerState,
|
||||||
|
state.updateFileManagerState,
|
||||||
|
])
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const [model, setModel] = useState<ModelInfo>(settings.model)
|
const [model, setModel] = useState<ModelInfo>(settings.model)
|
||||||
|
|
||||||
@ -110,6 +116,7 @@ export function SettingsDialog() {
|
|||||||
})
|
})
|
||||||
if (model.name !== settings.model.name) {
|
if (model.name !== settings.model.name) {
|
||||||
toggleOpenModelSwitching()
|
toggleOpenModelSwitching()
|
||||||
|
updateAppState({ disableShortCuts: true })
|
||||||
switchModel(model.name)
|
switchModel(model.name)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@ -126,14 +133,16 @@ export function SettingsDialog() {
|
|||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: `Switch to ${model.name} failed`,
|
title: `Switch to ${model.name} failed`,
|
||||||
})
|
})
|
||||||
|
setModel(settings.model)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
toggleOpenModelSwitching()
|
toggleOpenModelSwitching()
|
||||||
|
updateAppState({ disableShortCuts: false })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useHotkeys("s", () => {
|
useHotKey("s", () => {
|
||||||
toggleOpen()
|
toggleOpen()
|
||||||
onSubmit(form.getValues())
|
onSubmit(form.getValues())
|
||||||
})
|
})
|
||||||
@ -183,6 +192,12 @@ export function SettingsDialog() {
|
|||||||
for (let info of modelInfos) {
|
for (let info of modelInfos) {
|
||||||
if (model.name === info.name) {
|
if (model.name === info.name) {
|
||||||
defaultTab = info.model_type
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -384,9 +399,12 @@ export function SettingsDialog() {
|
|||||||
<AlertDialog open={openModelSwitching}>
|
<AlertDialog open={openModelSwitching}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogDescription>
|
{/* <AlertDialogDescription> */}
|
||||||
TODO: 添加加载动画 Switching to {model.name}
|
<div className="flex flex-col justify-center items-center gap-4">
|
||||||
</AlertDialogDescription>
|
<div>logo</div>
|
||||||
|
<div>Switching to {model.name}</div>
|
||||||
|
</div>
|
||||||
|
{/* </AlertDialogDescription> */}
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
@ -434,7 +452,7 @@ export function SettingsDialog() {
|
|||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
<div className="absolute right-10 bottom-6">
|
<div className="absolute right-10 bottom-6">
|
||||||
<Button onClick={() => toggleOpen()}>Ok</Button>
|
<Button onClick={() => onOpenChange(false)}>Ok</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "./ui/dialog"
|
} from "./ui/dialog"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
import useHotKey from "@/hooks/useHotkey"
|
||||||
|
|
||||||
interface ShortcutProps {
|
interface ShortcutProps {
|
||||||
content: string
|
content: string
|
||||||
@ -48,7 +48,7 @@ const CmdOrCtrl = () => {
|
|||||||
export function Shortcuts() {
|
export function Shortcuts() {
|
||||||
const [open, toggleOpen] = useToggle(false)
|
const [open, toggleOpen] = useToggle(false)
|
||||||
|
|
||||||
useHotkeys("h", () => {
|
useHotKey("h", () => {
|
||||||
toggleOpen()
|
toggleOpen()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { FormEvent, useState } from "react"
|
import { FormEvent, useState } from "react"
|
||||||
import { useToggle, useWindowSize } from "react-use"
|
import { useToggle, useWindowSize } from "react-use"
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states"
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
|
||||||
import { Switch } from "./ui/switch"
|
import { Switch } from "./ui/switch"
|
||||||
import { Label } from "./ui/label"
|
import { Label } from "./ui/label"
|
||||||
import { NumberInput } from "./ui/input"
|
import { NumberInput } from "./ui/input"
|
||||||
@ -15,38 +14,36 @@ import {
|
|||||||
} from "./ui/select"
|
} from "./ui/select"
|
||||||
import { Textarea } from "./ui/textarea"
|
import { Textarea } from "./ui/textarea"
|
||||||
import { SDSampler } from "@/lib/types"
|
import { SDSampler } from "@/lib/types"
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "./ui/accordion"
|
|
||||||
import { Separator } from "./ui/separator"
|
import { Separator } from "./ui/separator"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
|
||||||
import { ScrollArea } from "./ui/scroll-area"
|
import { ScrollArea } from "./ui/scroll-area"
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetDescription,
|
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
} from "./ui/sheet"
|
} from "./ui/sheet"
|
||||||
import { ChevronLeft } from "lucide-react"
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
import { Button } from "./ui/button"
|
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 SidePanel = () => {
|
||||||
const [settings, updateSettings, showSidePanel] = useStore((state) => [
|
const [settings, updateSettings, showSidePanel, runInpainting] = useStore(
|
||||||
state.settings,
|
(state) => [
|
||||||
state.updateSettings,
|
state.settings,
|
||||||
state.showSidePanel(),
|
state.updateSettings,
|
||||||
])
|
state.showSidePanel(),
|
||||||
const [open, toggleOpen] = useToggle(true)
|
state.runInpainting,
|
||||||
const [expandedAccordionItems, setExpandedAccordionItems] = useState<
|
]
|
||||||
string[]
|
)
|
||||||
>([])
|
const [open, toggleOpen] = useToggle(false)
|
||||||
|
|
||||||
useHotkeys("c", () => {
|
useHotKey("c", () => {
|
||||||
toggleOpen()
|
toggleOpen()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -58,13 +55,8 @@ const SidePanel = () => {
|
|||||||
|
|
||||||
const onKeyUp = (e: React.KeyboardEvent) => {
|
const onKeyUp = (e: React.KeyboardEvent) => {
|
||||||
// negativePrompt 回车触发 inpainting
|
// negativePrompt 回车触发 inpainting
|
||||||
if (
|
if (e.key === "Enter" && e.ctrlKey && settings.prompt.length !== 0) {
|
||||||
e.key === "Enter" &&
|
runInpainting()
|
||||||
e.ctrlKey &&
|
|
||||||
settings.prompt.length !== 0
|
|
||||||
// !isInpainting
|
|
||||||
) {
|
|
||||||
console.log("trigger negativePrompt")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,41 +67,62 @@ const SidePanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col items-start gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Label htmlFor="controlnet">Controlnet</Label>
|
<div className="flex justify-between items-center pr-2">
|
||||||
<Select
|
<Label htmlFor="controlnet">Controlnet</Label>
|
||||||
value={settings.controlnetMethod}
|
<Switch
|
||||||
onValueChange={(value) => {
|
id="controlnet"
|
||||||
updateSettings({ controlnetMethod: value })
|
checked={settings.enableControlNet}
|
||||||
}}
|
onCheckedChange={(value) => {
|
||||||
>
|
updateSettings({ enableControlNet: value })
|
||||||
<SelectTrigger id="controlnet">
|
}}
|
||||||
<SelectValue placeholder="Select control method" />
|
/>
|
||||||
</SelectTrigger>
|
</div>
|
||||||
<SelectContent align="end">
|
|
||||||
<SelectGroup>
|
<div className="pl-1 pr-2">
|
||||||
{Object.values(settings.model.controlnets).map((method) => (
|
<Select
|
||||||
<SelectItem key={method} value={method}>
|
value={settings.controlnetMethod}
|
||||||
{method.split("/")[1]}
|
onValueChange={(value) => {
|
||||||
</SelectItem>
|
updateSettings({ controlnetMethod: value })
|
||||||
))}
|
}}
|
||||||
</SelectGroup>
|
disabled={!settings.enableControlNet}
|
||||||
</SelectContent>
|
>
|
||||||
</Select>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select control method" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectGroup>
|
||||||
|
{Object.values(settings.model.controlnets).map((method) => (
|
||||||
|
<SelectItem key={method} value={method}>
|
||||||
|
{method.split("/")[1]}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="controlnet-weight">weight</Label>
|
<Label
|
||||||
|
htmlFor="controlnet-weight"
|
||||||
|
disabled={!settings.enableControlNet}
|
||||||
|
>
|
||||||
|
weight
|
||||||
|
</Label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="controlnet-weight"
|
id="controlnet-weight"
|
||||||
className="w-14"
|
className="w-14"
|
||||||
|
disabled={!settings.enableControlNet}
|
||||||
numberValue={settings.controlnetConditioningScale}
|
numberValue={settings.controlnetConditioningScale}
|
||||||
allowFloat
|
allowFloat
|
||||||
onNumberValueChange={(value) => {
|
onNumberValueChange={(value) => {
|
||||||
updateSettings({ controlnetConditioningScale: value })
|
updateSettings({ controlnetConditioningScale: value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -120,16 +133,19 @@ const SidePanel = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center">
|
<>
|
||||||
<Label htmlFor="lcm-lora">LCM Lora</Label>
|
<RowContainer>
|
||||||
<Switch
|
<Label htmlFor="lcm-lora">LCM Lora</Label>
|
||||||
id="lcm-lora"
|
<Switch
|
||||||
checked={settings.enableLCMLora}
|
id="lcm-lora"
|
||||||
onCheckedChange={(value) => {
|
checked={settings.enableLCMLora}
|
||||||
updateSettings({ enableLCMLora: value })
|
onCheckedChange={(value) => {
|
||||||
}}
|
updateSettings({ enableLCMLora: value })
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
|
</RowContainer>
|
||||||
|
<Separator />
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +156,7 @@ const SidePanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<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>
|
<Label htmlFor="freeu">Freeu</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id="freeu"
|
id="freeu"
|
||||||
@ -152,10 +168,13 @@ const SidePanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="flex flex-col gap-2 items-start">
|
<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
|
<NumberInput
|
||||||
id="freeu-s1"
|
id="freeu-s1"
|
||||||
className="w-14"
|
className="w-14"
|
||||||
|
disabled={!settings.enableFreeu}
|
||||||
numberValue={settings.freeuConfig.s1}
|
numberValue={settings.freeuConfig.s1}
|
||||||
allowFloat
|
allowFloat
|
||||||
onNumberValueChange={(value) => {
|
onNumberValueChange={(value) => {
|
||||||
@ -166,10 +185,13 @@ const SidePanel = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2 items-start">
|
<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
|
<NumberInput
|
||||||
id="freeu-s2"
|
id="freeu-s2"
|
||||||
className="w-14"
|
className="w-14"
|
||||||
|
disabled={!settings.enableFreeu}
|
||||||
numberValue={settings.freeuConfig.s2}
|
numberValue={settings.freeuConfig.s2}
|
||||||
allowFloat
|
allowFloat
|
||||||
onNumberValueChange={(value) => {
|
onNumberValueChange={(value) => {
|
||||||
@ -180,10 +202,13 @@ const SidePanel = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2 items-start">
|
<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
|
<NumberInput
|
||||||
id="freeu-b1"
|
id="freeu-b1"
|
||||||
className="w-14"
|
className="w-14"
|
||||||
|
disabled={!settings.enableFreeu}
|
||||||
numberValue={settings.freeuConfig.b1}
|
numberValue={settings.freeuConfig.b1}
|
||||||
allowFloat
|
allowFloat
|
||||||
onNumberValueChange={(value) => {
|
onNumberValueChange={(value) => {
|
||||||
@ -194,10 +219,13 @@ const SidePanel = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2 items-start">
|
<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
|
<NumberInput
|
||||||
id="freeu-b2"
|
id="freeu-b2"
|
||||||
className="w-14"
|
className="w-14"
|
||||||
|
disabled={!settings.enableFreeu}
|
||||||
numberValue={settings.freeuConfig.b2}
|
numberValue={settings.freeuConfig.b2}
|
||||||
allowFloat
|
allowFloat
|
||||||
onNumberValueChange={(value) => {
|
onNumberValueChange={(value) => {
|
||||||
@ -208,28 +236,51 @@ const SidePanel = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={open} onOpenChange={toggleOpen} modal={false}>
|
<Sheet open={open} modal={false}>
|
||||||
<SheetTrigger
|
<SheetTrigger
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className="z-10 outline-none absolute top-[68px] right-6 rounded-lg border bg-background"
|
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} />
|
<ChevronLeft strokeWidth={1} />
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
side="right"
|
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()}
|
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||||
onPointerDownOutside={(event) => event.preventDefault()}
|
onPointerDownOutside={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<SheetHeader className="mb-4">
|
<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 />
|
<Separator />
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
@ -237,7 +288,7 @@ const SidePanel = () => {
|
|||||||
className="pr-3"
|
className="pr-3"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="cropper">Cropper</Label>
|
<Label htmlFor="cropper">Cropper</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id="cropper"
|
id="cropper"
|
||||||
@ -246,9 +297,9 @@ const SidePanel = () => {
|
|||||||
updateSettings({ showCroper: value })
|
updateSettings({ showCroper: value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="steps">Steps</Label>
|
<Label htmlFor="steps">Steps</Label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="steps"
|
id="steps"
|
||||||
@ -259,9 +310,9 @@ const SidePanel = () => {
|
|||||||
updateSettings({ sdSteps: value })
|
updateSettings({ sdSteps: value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="guidance-scale">Guidance scale</Label>
|
<Label htmlFor="guidance-scale">Guidance scale</Label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="guidance-scale"
|
id="guidance-scale"
|
||||||
@ -272,22 +323,28 @@ const SidePanel = () => {
|
|||||||
updateSettings({ sdGuidanceScale: value })
|
updateSettings({ sdGuidanceScale: value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="strength">Strength</Label>
|
<div className="flex gap-2 items-center">
|
||||||
<NumberInput
|
<Label htmlFor="strength">Strength</Label>
|
||||||
id="strength"
|
<div className="text-sm">({settings.sdStrength})</div>
|
||||||
className="w-14"
|
</div>
|
||||||
numberValue={settings.sdStrength}
|
<Slider
|
||||||
allowFloat
|
className="w-24"
|
||||||
onNumberValueChange={(value) => {
|
defaultValue={[100]}
|
||||||
updateSettings({ sdStrength: value })
|
min={10}
|
||||||
}}
|
max={100}
|
||||||
|
step={1}
|
||||||
|
tabIndex={-1}
|
||||||
|
value={[Math.floor(settings.sdStrength * 100)]}
|
||||||
|
onValueChange={(vals) =>
|
||||||
|
updateSettings({ sdStrength: vals[0] / 100 })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="sampler">Sampler</Label>
|
<Label htmlFor="sampler">Sampler</Label>
|
||||||
<Select
|
<Select
|
||||||
value={settings.sdSampler as string}
|
value={settings.sdSampler as string}
|
||||||
@ -312,9 +369,9 @@ const SidePanel = () => {
|
|||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
{/* 每次会从服务器返回更新该值 */}
|
{/* 每次会从服务器返回更新该值 */}
|
||||||
<Label htmlFor="seed">Seed</Label>
|
<Label htmlFor="seed">Seed</Label>
|
||||||
<div className="flex gap-2 justify-center items-center">
|
<div className="flex gap-2 justify-center items-center">
|
||||||
@ -336,24 +393,26 @@ const SidePanel = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Label htmlFor="negative-prompt">Negative prompt</Label>
|
<Label htmlFor="negative-prompt">Negative prompt</Label>
|
||||||
<Textarea
|
<div className="pl-2 pr-4">
|
||||||
rows={4}
|
<Textarea
|
||||||
onKeyUp={onKeyUp}
|
rows={4}
|
||||||
className="max-h-[8rem] overflow-y-auto mb-2"
|
onKeyUp={onKeyUp}
|
||||||
placeholder=""
|
className="max-h-[8rem] overflow-y-auto mb-2"
|
||||||
id="negative-prompt"
|
placeholder=""
|
||||||
value={settings.negativePrompt}
|
id="negative-prompt"
|
||||||
onInput={(evt: FormEvent<HTMLTextAreaElement>) => {
|
value={settings.negativePrompt}
|
||||||
evt.preventDefault()
|
onInput={(evt: FormEvent<HTMLTextAreaElement>) => {
|
||||||
evt.stopPropagation()
|
evt.preventDefault()
|
||||||
const target = evt.target as HTMLTextAreaElement
|
evt.stopPropagation()
|
||||||
updateSettings({ negativePrompt: target.value })
|
const target = evt.target as HTMLTextAreaElement
|
||||||
}}
|
updateSettings({ negativePrompt: target.value })
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
@ -361,15 +420,10 @@ const SidePanel = () => {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{renderConterNetSetting()}
|
{renderConterNetSetting()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
{renderFreeu()}
|
{renderFreeu()}
|
||||||
|
|
||||||
<Separator />
|
|
||||||
{renderLCMLora()}
|
{renderLCMLora()}
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="mask-blur">Mask blur</Label>
|
<Label htmlFor="mask-blur">Mask blur</Label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="mask-blur"
|
id="mask-blur"
|
||||||
@ -380,9 +434,9 @@ const SidePanel = () => {
|
|||||||
updateSettings({ sdMaskBlur: value })
|
updateSettings({ sdMaskBlur: value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
|
|
||||||
<div className="flex justify-between items-center">
|
<RowContainer>
|
||||||
<Label htmlFor="match-histograms">Match histograms</Label>
|
<Label htmlFor="match-histograms">Match histograms</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id="match-histograms"
|
id="match-histograms"
|
||||||
@ -391,7 +445,7 @@ const SidePanel = () => {
|
|||||||
updateSettings({ sdMatchHistograms: value })
|
updateSettings({ sdMatchHistograms: value })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RowContainer>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
@ -52,8 +52,12 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button"
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(
|
||||||
|
buttonVariants({ variant, size, className }),
|
||||||
|
"outline-none cursor-default"
|
||||||
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
tabIndex={-1}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -37,7 +37,8 @@ const DialogContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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",
|
"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()}
|
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -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",
|
"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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
|
||||||
onCloseAutoFocus={(e) => e.preventDefault()}
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
))
|
))
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { FocusEvent } from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ 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 (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
@ -16,6 +27,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
tabIndex={-1}
|
||||||
|
onFocus={handleOnFocus}
|
||||||
|
onBlur={handleOnBlur}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -8,14 +8,19 @@ const labelVariants = cva(
|
|||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface LabelProps {
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
const Label = React.forwardRef<
|
const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
VariantProps<typeof labelVariants>
|
VariantProps<typeof labelVariants> &
|
||||||
>(({ className, ...props }, ref) => (
|
LabelProps
|
||||||
|
>(({ className, disabled, ...props }, ref) => (
|
||||||
<LabelPrimitive.Root
|
<LabelPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(labelVariants(), className)}
|
className={cn(labelVariants(), className, disabled ? "opacity-50" : "")}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@ -29,7 +28,7 @@ const SheetOverlay = React.forwardRef<
|
|||||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
const sheetVariants = cva(
|
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: {
|
variants: {
|
||||||
side: {
|
side: {
|
||||||
@ -63,10 +62,6 @@ const SheetContent = React.forwardRef<
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{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>
|
</SheetPrimitive.Content>
|
||||||
</SheetPortal>
|
</SheetPortal>
|
||||||
))
|
))
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
html { font-family: "Inter", "system-ui"; }
|
html { font-family: "Inter", "system-ui"; overflow: hidden; }
|
||||||
|
|
||||||
@supports (font-variation-settings: normal) {
|
@supports (font-variation-settings: normal) {
|
||||||
html { font-family: "Inter var", "system-ui"; }
|
html { font-family: "Inter var", "system-ui"; }
|
||||||
|
11
web_app/src/hooks/useHotkey.tsx
Normal file
11
web_app/src/hooks/useHotkey.tsx
Normal 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
|
@ -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_DIFFUSERS_SDXL_INPAINT = "diffusers_sdxl_inpaint"
|
||||||
export const MODEL_TYPE_OTHER = "diffusers_other"
|
export const MODEL_TYPE_OTHER = "diffusers_other"
|
||||||
export const BRUSH_COLOR = "#ffcc00bb"
|
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"
|
||||||
|
@ -18,7 +18,13 @@ import {
|
|||||||
SortBy,
|
SortBy,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from "./types"
|
} 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 { dataURItoBlob, generateMask, loadImage, srcToFile } from "./utils"
|
||||||
import inpaint, { runPlugin } from "./api"
|
import inpaint, { runPlugin } from "./api"
|
||||||
import { toast, useToast } from "@/components/ui/use-toast"
|
import { toast, useToast } from "@/components/ui/use-toast"
|
||||||
@ -84,6 +90,7 @@ export type Settings = {
|
|||||||
p2pGuidanceScale: number
|
p2pGuidanceScale: number
|
||||||
|
|
||||||
// ControlNet
|
// ControlNet
|
||||||
|
enableControlNet: boolean
|
||||||
controlnetConditioningScale: number
|
controlnetConditioningScale: number
|
||||||
controlnetMethod: string
|
controlnetMethod: string
|
||||||
|
|
||||||
@ -122,8 +129,6 @@ type EditorState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
idForUpdateView: string
|
|
||||||
|
|
||||||
file: File | null
|
file: File | null
|
||||||
customMask: File | null
|
customMask: File | null
|
||||||
imageHeight: number
|
imageHeight: number
|
||||||
@ -132,6 +137,7 @@ type AppState = {
|
|||||||
isPluginRunning: boolean
|
isPluginRunning: boolean
|
||||||
windowSize: Size
|
windowSize: Size
|
||||||
editorState: EditorState
|
editorState: EditorState
|
||||||
|
disableShortCuts: boolean
|
||||||
|
|
||||||
interactiveSegState: InteractiveSegState
|
interactiveSegState: InteractiveSegState
|
||||||
fileManagerState: FileManagerState
|
fileManagerState: FileManagerState
|
||||||
@ -188,14 +194,13 @@ type AppAction = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultValues: AppState = {
|
const defaultValues: AppState = {
|
||||||
idForUpdateView: nanoid(),
|
|
||||||
|
|
||||||
file: null,
|
file: null,
|
||||||
customMask: null,
|
customMask: null,
|
||||||
imageHeight: 0,
|
imageHeight: 0,
|
||||||
imageWidth: 0,
|
imageWidth: 0,
|
||||||
isInpainting: false,
|
isInpainting: false,
|
||||||
isPluginRunning: false,
|
isPluginRunning: false,
|
||||||
|
disableShortCuts: false,
|
||||||
|
|
||||||
windowSize: {
|
windowSize: {
|
||||||
height: 600,
|
height: 600,
|
||||||
@ -254,6 +259,7 @@ const defaultValues: AppState = {
|
|||||||
is_single_file_diffusers: false,
|
is_single_file_diffusers: false,
|
||||||
need_prompt: false,
|
need_prompt: false,
|
||||||
},
|
},
|
||||||
|
enableControlNet: false,
|
||||||
showCroper: false,
|
showCroper: false,
|
||||||
enableDownloadMask: false,
|
enableDownloadMask: false,
|
||||||
enableManualInpainting: false,
|
enableManualInpainting: false,
|
||||||
@ -311,7 +317,17 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
runInpainting: async () => {
|
runInpainting: async () => {
|
||||||
const { file, imageWidth, imageHeight, settings, cropperState } = get()
|
const {
|
||||||
|
isInpainting,
|
||||||
|
file,
|
||||||
|
imageWidth,
|
||||||
|
imageHeight,
|
||||||
|
settings,
|
||||||
|
cropperState,
|
||||||
|
} = get()
|
||||||
|
if (isInpainting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (file === null) {
|
if (file === null) {
|
||||||
return
|
return
|
||||||
@ -656,13 +672,16 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
showPromptInput: (): boolean => {
|
showPromptInput: (): boolean => {
|
||||||
const model_type = get().settings.model.model_type
|
const model = get().settings.model
|
||||||
return ["diffusers_sd", "diffusers_sd_inpaint"].includes(model_type)
|
return (
|
||||||
|
model.model_type !== MODEL_TYPE_INPAINT &&
|
||||||
|
model.name !== PAINT_BY_EXAMPLE
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
showSidePanel: (): boolean => {
|
showSidePanel: (): boolean => {
|
||||||
const model_type = get().settings.model.model_type
|
const model = get().settings.model
|
||||||
return ["diffusers_sd", "diffusers_sd_inpaint"].includes(model_type)
|
return model.model_type !== MODEL_TYPE_INPAINT
|
||||||
},
|
},
|
||||||
|
|
||||||
setServerConfig: (newValue: ServerConfig) => {
|
setServerConfig: (newValue: ServerConfig) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user