diff --git a/web_app/src/assets/kofi_button_black.png b/web_app/src/assets/kofi_button_black.png new file mode 100644 index 0000000..0baa366 Binary files /dev/null and b/web_app/src/assets/kofi_button_black.png differ diff --git a/web_app/src/components/Coffee.tsx b/web_app/src/components/Coffee.tsx new file mode 100644 index 0000000..16b110d --- /dev/null +++ b/web_app/src/components/Coffee.tsx @@ -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 ( + + + + + + + + Buy me a coffee + + Hi, if you found my project is useful, please conside buy me a coffee + to support my work. Thanks! + +
+ + + +
+
+
+ ) +} + +export default Coffee diff --git a/web_app/src/components/Editor.tsx b/web_app/src/components/Editor.tsx index 54ddfa7..0bdce28 100644 --- a/web_app/src/components/Editor.tsx +++ b/web_app/src/components/Editor.tsx @@ -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,16 +654,20 @@ export default function Editor(props: EditorProps) { useKeyPressEvent( " ", (ev) => { - ev?.preventDefault() - ev?.stopPropagation() - setShowBrush(false) - setIsPanning(true) + if (!disableShortCuts) { + ev?.preventDefault() + ev?.stopPropagation() + setShowBrush(false) + setIsPanning(true) + } }, (ev) => { - ev?.preventDefault() - ev?.stopPropagation() - setShowBrush(true) - setIsPanning(false) + if (!disableShortCuts) { + ev?.preventDefault() + ev?.stopPropagation() + setShowBrush(true) + setIsPanning(false) + } } ) @@ -738,7 +741,6 @@ export default function Editor(props: EditorProps) { const renderCanvas = () => { return ( { 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))} -
+
{ + useHotKey("f", () => { toggleOpen() }) diff --git a/web_app/src/components/Header.tsx b/web_app/src/components/Header.tsx index 8322f25..848886d 100644 --- a/web_app/src/components/Header.tsx +++ b/web_app/src/components/Header.tsx @@ -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 ? : <>}
- {/* */} +
diff --git a/web_app/src/components/PromptInput.tsx b/web_app/src/components/PromptInput.tsx index eadffe5..6bb539a 100644 --- a/web_app/src/components/PromptInput.tsx +++ b/web_app/src/components/PromptInput.tsx @@ -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(ref, () => { + if (ref?.current) { + const input = ref.current as HTMLInputElement + input.blur() + } + }) const handleOnInput = (evt: FormEvent) => { evt.preventDefault() @@ -43,6 +52,7 @@ const PromptInput = () => { return (
[ - 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 }) => ( +
{children}
+) const SidePanel = () => { - const [settings, updateSettings, showSidePanel] = useStore((state) => [ - state.settings, - state.updateSettings, - state.showSidePanel(), - ]) - const [open, toggleOpen] = useToggle(true) - const [expandedAccordionItems, setExpandedAccordionItems] = useState< - string[] - >([]) + const [settings, updateSettings, showSidePanel, runInpainting] = useStore( + (state) => [ + state.settings, + state.updateSettings, + state.showSidePanel(), + 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,41 +67,62 @@ const SidePanel = () => { return (
-
- - +
+
+ + { + 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 = () => { }} />
-
+
-