From ef7917796607d112e2d3a1f9a0a2b64e02fb7f0f Mon Sep 17 00:00:00 2001 From: Qing Date: Sun, 3 Dec 2023 14:25:06 +0800 Subject: [PATCH] wip --- web_app/package-lock.json | 6 + web_app/package.json | 1 + web_app/src/components/Cropper.tsx | 65 ++- web_app/src/components/Editor.tsx | 3 +- web_app/src/components/Header.tsx | 2 +- web_app/src/components/InteractiveSeg.tsx | 2 - web_app/src/components/Settings.tsx | 62 ++- web_app/src/components/SidePanel.tsx | 397 ++++++++++++++++ web_app/src/components/Workspace.tsx | 2 + web_app/src/components/ui/accordion.tsx | 2 +- web_app/src/components/ui/input.tsx | 47 +- web_app/src/components/ui/popover.tsx | 1 + web_app/src/components/ui/sheet.tsx | 138 ++++++ web_app/src/components/ui/textarea.tsx | 1 + web_app/src/globals.css | 30 +- web_app/src/lib/const.ts | 6 + web_app/src/lib/states.ts | 33 +- web_app/src/lib/store.ts | 539 ---------------------- web_app/src/lib/types.ts | 18 + web_app/src/main.tsx | 1 + 20 files changed, 742 insertions(+), 614 deletions(-) create mode 100644 web_app/src/components/SidePanel.tsx create mode 100644 web_app/src/components/ui/sheet.tsx delete mode 100644 web_app/src/lib/store.ts diff --git a/web_app/package-lock.json b/web_app/package-lock.json index bfa6edd..2fe2620 100644 --- a/web_app/package-lock.json +++ b/web_app/package-lock.json @@ -34,6 +34,7 @@ "clsx": "^2.0.0", "flexsearch": "^0.7.21", "immer": "^10.0.3", + "inter-ui": "^3.19.3", "lodash": "^4.17.21", "lucide-react": "^0.292.0", "mitt": "^3.0.1", @@ -4468,6 +4469,11 @@ "fast-loops": "^1.1.3" } }, + "node_modules/inter-ui": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/inter-ui/-/inter-ui-3.19.3.tgz", + "integrity": "sha512-5FG9fjuYOXocIfjzcCBhICL5cpvwEetseL3FU6tP3d6Bn7g8wODhB+I9RNGRTizCT7CUG4GOK54OPxqq3msQgg==" + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/web_app/package.json b/web_app/package.json index 9b07e24..81e692d 100644 --- a/web_app/package.json +++ b/web_app/package.json @@ -36,6 +36,7 @@ "clsx": "^2.0.0", "flexsearch": "^0.7.21", "immer": "^10.0.3", + "inter-ui": "^3.19.3", "lodash": "^4.17.21", "lucide-react": "^0.292.0", "mitt": "^3.0.1", diff --git a/web_app/src/components/Cropper.tsx b/web_app/src/components/Cropper.tsx index 3a10420..87f7b94 100644 --- a/web_app/src/components/Cropper.tsx +++ b/web_app/src/components/Cropper.tsx @@ -63,6 +63,8 @@ const Cropper = (props: Props) => { const { minHeight, minWidth, maxHeight, maxWidth, scale, show } = props const [ + imageWidth, + imageHeight, isInpainting, { x, y, width, height }, setX, @@ -70,6 +72,8 @@ const Cropper = (props: Props) => { setWidth, setHeight, ] = useStore((state) => [ + state.imageWidth, + state.imageHeight, state.isInpainting, state.cropperState, state.setCropperX, @@ -84,7 +88,9 @@ const Cropper = (props: Props) => { useEffect(() => { setX(Math.round((maxWidth - 512) / 2)) setY(Math.round((maxHeight - 512) / 2)) - }, [maxHeight, maxWidth]) + // TODO: 换了一张较小的图片,cropper 的起始位置和边界要修改 + // TODO: 一开始的 scale 不对 + }, [maxHeight, maxWidth, imageWidth, imageHeight]) const [evData, setEVData] = useState({ initX: 0, @@ -253,25 +259,33 @@ const Cropper = (props: Props) => { const createDragHandle = (cursor: string, side1: string, side2: string) => { const sideLength = 12 - const draghandleCls = `w-[${sideLength}px] h-[${sideLength}px] z-4 absolute block border-2 border-primary borde pointer-events-auto hover:bg-primary` + const halfSideLength = sideLength / 2 + const draghandleCls = `w-[${sideLength}px] h-[${sideLength}px] z-[4] absolute content-[''] block border-2 border-primary borde pointer-events-auto hover:bg-primary` - let side2Cls = `${side2}-[-${sideLength / 2}px]` + let xTrans = "0" + let yTrans = "0" + + let side2Key = side2 + let side2Val = `${-halfSideLength}px` if (side2 === "") { - if (side1 === "top" || side1 === "bottom") { - side2Cls = `left-[calc(50%-${sideLength / 2}px)]` - } else if (side1 === "left" || side1 === "right") { - side2Cls = `top-[calc(50%-${sideLength / 2}px)]` + side2Val = "50%" + if (side1 === "left" || side1 === "right") { + side2Key = "top" + yTrans = "-50%" + } else { + side2Key = "left" + xTrans = "-50%" } } return (
{ const createCropSelection = () => { return ( -
+
{ className="absolute pointer-events-auto top-0 left-0 h-full cursor-ew-resize w-[12px] ml-[-6px]" data-ord="left" /> - {createDragHandle("cursor-nw-resize", "top", "left")} {createDragHandle("cursor-ne-resize", "top", "right")} - {createDragHandle("cursor-se-resize", "bottom", "left")} - {createDragHandle("cursor-sw-resize", "bottom", "right")} - + {createDragHandle("cursor-sw-resize", "bottom", "left")} + {createDragHandle("cursor-se-resize", "bottom", "right")} {createDragHandle("cursor-ns-resize", "top", "")} {createDragHandle("cursor-ns-resize", "bottom", "")} {createDragHandle("cursor-ew-resize", "left", "")} @@ -351,19 +367,20 @@ const Cropper = (props: Props) => { style={{ height, width, - outlineWidth: `${DRAG_HANDLE_BORDER / scale}px`, + outlineWidth: `${(DRAG_HANDLE_BORDER / scale) * 1.3}px`, }} /> ) } + if (show === false) { + return null + } + return ( -
+
{createBorder()} diff --git a/web_app/src/components/Editor.tsx b/web_app/src/components/Editor.tsx index 1a3998e..eed9bd5 100644 --- a/web_app/src/components/Editor.tsx +++ b/web_app/src/components/Editor.tsx @@ -1491,8 +1491,7 @@ export default function Editor(props: EditorProps) { minHeight={Math.min(256, imageHeight)} minWidth={Math.min(256, imageWidth)} scale={getCurScale()} - // show={settings.showCroper} - show={true} + show={settings.showCroper} /> {/* {interactiveSegState.isInteractiveSeg ? : <>} */} diff --git a/web_app/src/components/Header.tsx b/web_app/src/components/Header.tsx index 81fa016..1f4985a 100644 --- a/web_app/src/components/Header.tsx +++ b/web_app/src/components/Header.tsx @@ -38,7 +38,7 @@ const Header = () => { state.serverConfig.enableFileManager, state.settings.enableManualInpainting, state.settings.enableUploadMask, - state.shouldShowPromptInput(), + state.showPromptInput(), state.setFile, state.setCustomFile, ]) diff --git a/web_app/src/components/InteractiveSeg.tsx b/web_app/src/components/InteractiveSeg.tsx index d57539e..764a177 100644 --- a/web_app/src/components/InteractiveSeg.tsx +++ b/web_app/src/components/InteractiveSeg.tsx @@ -1,8 +1,6 @@ import { useStore } from "@/lib/states" import { Button } from "./ui/button" import { Dialog, DialogContent, DialogTitle } from "./ui/dialog" -import { MousePointerClick } from "lucide-react" -import { DropdownMenuItem } from "./ui/dropdown-menu" interface InteractiveSegReplaceModal { show: boolean diff --git a/web_app/src/components/Settings.tsx b/web_app/src/components/Settings.tsx index 86aac32..64ff906 100644 --- a/web_app/src/components/Settings.tsx +++ b/web_app/src/components/Settings.tsx @@ -20,7 +20,7 @@ import { import { Input } from "@/components/ui/input" import { Switch } from "./ui/switch" import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" -import { useState } from "react" +import { useEffect, useState } from "react" import { cn } from "@/lib/utils" import { useQuery } from "@tanstack/react-query" import { fetchModelInfos, switchModel } from "@/lib/api" @@ -34,6 +34,14 @@ import { AlertDialogDescription, AlertDialogHeader, } from "./ui/alert-dialog" +import { + MODEL_TYPE_DIFFUSERS_SD, + MODEL_TYPE_DIFFUSERS_SDXL, + MODEL_TYPE_DIFFUSERS_SDXL_INPAINT, + MODEL_TYPE_DIFFUSERS_SD_INPAINT, + MODEL_TYPE_INPAINT, + MODEL_TYPE_OTHER, +} from "@/lib/const" const formSchema = z.object({ enableFileManager: z.boolean(), @@ -59,7 +67,7 @@ const TAB_NAMES = [TAB_MODEL, TAB_GENERAL] export function SettingsDialog() { const [open, toggleOpen] = useToggle(false) const [openModelSwitching, toggleOpenModelSwitching] = useToggle(false) - const [tab, setTab] = useState(TAB_GENERAL) + const [tab, setTab] = useState(TAB_MODEL) const [settings, updateSettings, fileManagerState, updateFileManagerState] = useStore((state) => [ state.settings, @@ -70,7 +78,7 @@ export function SettingsDialog() { const { toast } = useToast() const [model, setModel] = useState(settings.model) - const { data: modelInfos, isSuccess } = useQuery({ + const { data: modelInfos, status } = useQuery({ queryKey: ["modelInfos"], queryFn: fetchModelInfos, }) @@ -82,7 +90,6 @@ export function SettingsDialog() { enableDownloadMask: settings.enableDownloadMask, enableManualInpainting: settings.enableManualInpainting, enableUploadMask: settings.enableUploadMask, - enableFileManager: fileManagerState.enabled, inputDirectory: fileManagerState.inputDirectory, outputDirectory: fileManagerState.outputDirectory, }, @@ -98,11 +105,9 @@ export function SettingsDialog() { // TODO: validate input/output Directory updateFileManagerState({ - enabled: values.enableFileManager, inputDirectory: values.inputDirectory, outputDirectory: values.outputDirectory, }) - if (model.name !== settings.model.name) { toggleOpenModelSwitching() switchModel(model.name) @@ -127,19 +132,21 @@ export function SettingsDialog() { }) } } + useHotkeys("s", () => { toggleOpen() - form.handleSubmit(onSubmit)() + onSubmit(form.getValues()) }) function onOpenChange(value: boolean) { toggleOpen() if (!value) { - form.handleSubmit(onSubmit)() + onSubmit(form.getValues()) } } function onModelSelect(info: ModelInfo) { + console.log(info) setModel(info) } @@ -168,11 +175,11 @@ export function SettingsDialog() { } function renderModelSettings() { - if (!isSuccess) { + if (status !== "success") { return <> } - let defaultTab = "inpaint" + let defaultTab = MODEL_TYPE_INPAINT for (let info of modelInfos) { if (model.name === info.name) { defaultTab = info.model_type @@ -198,28 +205,35 @@ export function SettingsDialog() {
- Inpaint - Diffusion - + Inpaint + + Diffusion + + Diffusion inpaint - Diffusion other + + Diffusion other + - - {renderModelList(["inpaint"])} + + {renderModelList([MODEL_TYPE_INPAINT])} - - {renderModelList(["diffusers_sd", "diffusers_sdxl"])} - - + {renderModelList([ - "diffusers_sd_inpaint", - "diffusers_sdxl_inpaint", + MODEL_TYPE_DIFFUSERS_SD, + MODEL_TYPE_DIFFUSERS_SDXL, ])} - - {renderModelList(["diffusers_other"])} + + {renderModelList([ + MODEL_TYPE_DIFFUSERS_SD_INPAINT, + MODEL_TYPE_DIFFUSERS_SDXL_INPAINT, + ])} + + + {renderModelList([MODEL_TYPE_OTHER])} diff --git a/web_app/src/components/SidePanel.tsx b/web_app/src/components/SidePanel.tsx new file mode 100644 index 0000000..d78fbf9 --- /dev/null +++ b/web_app/src/components/SidePanel.tsx @@ -0,0 +1,397 @@ +import { FormEvent, useState } from "react" +import { useToggle } 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" +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} 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, SheetHeader } from "./ui/sheet" + +const SidePanel = () => { + const [settings, updateSettings, showSidePanel] = useStore((state) => [ + state.settings, + state.updateSettings, + state.showSidePanel(), + ]) + const [open, toggleOpen] = useToggle(true) + const [expandedAccordionItems, setExpandedAccordionItems] = useState< + string[] + >([]) + + useHotkeys("c", () => { + toggleOpen() + }) + + if (!showSidePanel) { + return null + } + + const onKeyUp = (e: React.KeyboardEvent) => { + // negativePrompt 回车触发 inpainting + if ( + e.key === "Enter" && + e.ctrlKey && + settings.prompt.length !== 0 + // !isInpainting + ) { + console.log("trigger negativePrompt") + } + } + + const renderConterNetSetting = () => { + if (!settings.model.support_controlnet) { + return null + } + + return ( +
+
+ {/* */} + +
+ +
+ + { + updateSettings({ controlnetConditioningScale: value }) + }} + /> +
+
+ ) + } + + const renderLCMLora = () => { + if (!settings.model.support_lcm_lora) { + return null + } + + return ( +
+ + { + updateSettings({ enableLCMLora: value }) + }} + /> +
+ ) + } + + const renderFreeu = () => { + if (!settings.model.support_freeu) { + return null + } + + return ( +
+
+ + { + updateSettings({ enableFreeu: value }) + }} + /> +
+
+
+ + { + updateSettings({ + freeuConfig: { ...settings.freeuConfig, s1: value }, + }) + }} + /> +
+
+ + { + updateSettings({ + freeuConfig: { ...settings.freeuConfig, s2: value }, + }) + }} + /> +
+
+ + { + updateSettings({ + freeuConfig: { ...settings.freeuConfig, b1: value }, + }) + }} + /> +
+
+ + { + updateSettings({ + freeuConfig: { ...settings.freeuConfig, b2: value }, + }) + }} + /> +
+
+
+ ) + } + + return ( + + + Config + + event.preventDefault()} + onOpenAutoFocus={(event) => event.preventDefault()} + onPointerDownOutside={(event) => event.preventDefault()} + > + + +
+
+ + { + updateSettings({ showCroper: value }) + }} + /> +
+ +
+ + { + updateSettings({ sdSteps: value }) + }} + /> +
+ +
+ + { + updateSettings({ sdGuidanceScale: value }) + }} + /> +
+ +
+ + { + updateSettings({ sdStrength: value }) + }} + /> +
+ +
+ + +
+ +
+ {/* 每次会从服务器返回更新该值 */} + +
+ { + updateSettings({ seedFixed: value }) + }} + /> + { + updateSettings({ seed: val }) + }} + /> +
+
+ +
+ + + Negative prompt + +