update FE
This commit is contained in:
parent
5d726d51ab
commit
ee3809670b
|
|
@ -1,167 +1,169 @@
|
||||||
import { useCallback, useEffect, useRef } from "react"
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import useInputImage from "@/hooks/useInputImage"
|
import useInputImage from "@/hooks/useInputImage";
|
||||||
import { keepGUIAlive } from "@/lib/utils"
|
import { keepGUIAlive } from "@/lib/utils";
|
||||||
import { getServerConfig } from "@/lib/api"
|
import { getServerConfig } from "@/lib/api";
|
||||||
import Header from "@/components/Header"
|
import Header from "@/components/Header";
|
||||||
import Workspace from "@/components/Workspace"
|
import Workspace from "@/components/Workspace";
|
||||||
import FileSelect from "@/components/FileSelect"
|
import FileSelect from "@/components/FileSelect";
|
||||||
import { Toaster } from "./components/ui/toaster"
|
import { Toaster } from "./components/ui/toaster";
|
||||||
import { useStore } from "./lib/states"
|
import { useStore } from "./lib/states";
|
||||||
import { useWindowSize } from "react-use"
|
import { useWindowSize } from "react-use";
|
||||||
|
|
||||||
const SUPPORTED_FILE_TYPE = [
|
const SUPPORTED_FILE_TYPE = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
"image/png",
|
"image/png",
|
||||||
"image/webp",
|
"image/webp",
|
||||||
"image/bmp",
|
"image/bmp",
|
||||||
"image/tiff",
|
"image/tiff",
|
||||||
]
|
];
|
||||||
function Home() {
|
function Home() {
|
||||||
const [file, updateAppState, setServerConfig, setFile] = useStore((state) => [
|
const [file, updateAppState, setServerConfig, setFile] = useStore(
|
||||||
state.file,
|
(state) => [
|
||||||
state.updateAppState,
|
state.file,
|
||||||
state.setServerConfig,
|
state.updateAppState,
|
||||||
state.setFile,
|
state.setServerConfig,
|
||||||
])
|
state.setFile,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const userInputImage = useInputImage()
|
const userInputImage = useInputImage();
|
||||||
|
|
||||||
const windowSize = useWindowSize()
|
const windowSize = useWindowSize();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userInputImage) {
|
if (userInputImage) {
|
||||||
setFile(userInputImage)
|
setFile(userInputImage);
|
||||||
}
|
}
|
||||||
}, [userInputImage, setFile])
|
}, [userInputImage, setFile]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateAppState({ windowSize })
|
updateAppState({ windowSize });
|
||||||
}, [windowSize])
|
}, [windowSize]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchServerConfig = async () => {
|
const fetchServerConfig = async () => {
|
||||||
const serverConfig = await getServerConfig()
|
const serverConfig = await getServerConfig();
|
||||||
setServerConfig(serverConfig)
|
setServerConfig(serverConfig);
|
||||||
if (serverConfig.isDesktop) {
|
if (serverConfig.isDesktop) {
|
||||||
// Keeping GUI Window Open
|
// Keeping GUI Window Open
|
||||||
keepGUIAlive()
|
keepGUIAlive();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
fetchServerConfig()
|
fetchServerConfig();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const dragCounter = useRef(0)
|
const dragCounter = useRef(0);
|
||||||
|
|
||||||
const handleDrag = useCallback((event: any) => {
|
const handleDrag = useCallback((event: any) => {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleDragIn = useCallback((event: any) => {
|
const handleDragIn = useCallback((event: any) => {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
dragCounter.current += 1
|
dragCounter.current += 1;
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleDragOut = useCallback((event: any) => {
|
const handleDragOut = useCallback((event: any) => {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
dragCounter.current -= 1
|
dragCounter.current -= 1;
|
||||||
if (dragCounter.current > 0) return
|
if (dragCounter.current > 0) return;
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const handleDrop = useCallback((event: any) => {
|
const handleDrop = useCallback((event: any) => {
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
|
if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
|
||||||
if (event.dataTransfer.files.length > 1) {
|
if (event.dataTransfer.files.length > 1) {
|
||||||
// setToastState({
|
// setToastState({
|
||||||
// open: true,
|
// open: true,
|
||||||
// desc: "Please drag and drop only one file",
|
// desc: "Please drag and drop only one file",
|
||||||
// state: "error",
|
// state: "error",
|
||||||
// duration: 3000,
|
// duration: 3000,
|
||||||
// })
|
// })
|
||||||
} else {
|
} else {
|
||||||
const dragFile = event.dataTransfer.files[0]
|
const dragFile = event.dataTransfer.files[0];
|
||||||
const fileType = dragFile.type
|
const fileType = dragFile.type;
|
||||||
if (SUPPORTED_FILE_TYPE.includes(fileType)) {
|
if (SUPPORTED_FILE_TYPE.includes(fileType)) {
|
||||||
setFile(dragFile)
|
setFile(dragFile);
|
||||||
} else {
|
} else {
|
||||||
// setToastState({
|
// setToastState({
|
||||||
// open: true,
|
// open: true,
|
||||||
// desc: "Please drag and drop an image file",
|
// desc: "Please drag and drop an image file",
|
||||||
// state: "error",
|
// state: "error",
|
||||||
// duration: 3000,
|
// duration: 3000,
|
||||||
// })
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.dataTransfer.clearData()
|
event.dataTransfer.clearData();
|
||||||
}
|
}
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const onPaste = useCallback((event: any) => {
|
const onPaste = useCallback((event: any) => {
|
||||||
// TODO: when sd side panel open, ctrl+v not work
|
// TODO: when sd side panel open, ctrl+v not work
|
||||||
// https://htmldom.dev/paste-an-image-from-the-clipboard/
|
// https://htmldom.dev/paste-an-image-from-the-clipboard/
|
||||||
if (!event.clipboardData) {
|
if (!event.clipboardData) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const clipboardItems = event.clipboardData.items
|
const clipboardItems = event.clipboardData.items;
|
||||||
const items: DataTransferItem[] = [].slice
|
const items: DataTransferItem[] = [].slice
|
||||||
.call(clipboardItems)
|
.call(clipboardItems)
|
||||||
.filter((item: DataTransferItem) => {
|
.filter((item: DataTransferItem) => {
|
||||||
// Filter the image items only
|
// Filter the image items only
|
||||||
return item.type.indexOf("image") !== -1
|
return item.type.indexOf("image") !== -1;
|
||||||
})
|
});
|
||||||
|
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault();
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
|
|
||||||
// TODO: add confirm dialog
|
// TODO: add confirm dialog
|
||||||
|
|
||||||
const item = items[0]
|
const item = items[0];
|
||||||
// Get the blob of image
|
// Get the blob of image
|
||||||
const blob = item.getAsFile()
|
const blob = item.getAsFile();
|
||||||
if (blob) {
|
if (blob) {
|
||||||
setFile(blob)
|
setFile(blob);
|
||||||
}
|
}
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("dragenter", handleDragIn)
|
window.addEventListener("dragenter", handleDragIn);
|
||||||
window.addEventListener("dragleave", handleDragOut)
|
window.addEventListener("dragleave", handleDragOut);
|
||||||
window.addEventListener("dragover", handleDrag)
|
window.addEventListener("dragover", handleDrag);
|
||||||
window.addEventListener("drop", handleDrop)
|
window.addEventListener("drop", handleDrop);
|
||||||
window.addEventListener("paste", onPaste)
|
window.addEventListener("paste", onPaste);
|
||||||
return function cleanUp() {
|
return function cleanUp() {
|
||||||
window.removeEventListener("dragenter", handleDragIn)
|
window.removeEventListener("dragenter", handleDragIn);
|
||||||
window.removeEventListener("dragleave", handleDragOut)
|
window.removeEventListener("dragleave", handleDragOut);
|
||||||
window.removeEventListener("dragover", handleDrag)
|
window.removeEventListener("dragover", handleDrag);
|
||||||
window.removeEventListener("drop", handleDrop)
|
window.removeEventListener("drop", handleDrop);
|
||||||
window.removeEventListener("paste", onPaste)
|
window.removeEventListener("paste", onPaste);
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
|
<main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Header />
|
<Header />
|
||||||
<Workspace />
|
<Workspace />
|
||||||
{!file ? (
|
{!file ? (
|
||||||
<FileSelect
|
<FileSelect
|
||||||
onSelection={async (f) => {
|
onSelection={async (f) => {
|
||||||
setFile(f)
|
setFile(f);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,71 +1,71 @@
|
||||||
import { useState } from "react"
|
import { useState } from "react";
|
||||||
import useResolution from "@/hooks/useResolution"
|
import useResolution from "@/hooks/useResolution";
|
||||||
|
|
||||||
type FileSelectProps = {
|
type FileSelectProps = {
|
||||||
onSelection: (file: File) => void
|
onSelection: (file: File) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function FileSelect(props: FileSelectProps) {
|
export default function FileSelect(props: FileSelectProps) {
|
||||||
const { onSelection } = props
|
const { onSelection } = props;
|
||||||
|
|
||||||
const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
|
const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`);
|
||||||
|
|
||||||
const resolution = useResolution()
|
const resolution = useResolution();
|
||||||
|
|
||||||
function onFileSelected(file: File) {
|
function onFileSelected(file: File) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
// Skip non-image files
|
// Skip non-image files
|
||||||
const isImage = file.type.match("image.*")
|
const isImage = file.type.match("image.*");
|
||||||
if (!isImage) {
|
if (!isImage) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Check if file is larger than 20mb
|
// Check if file is larger than 20mb
|
||||||
if (file.size > 20 * 1024 * 1024) {
|
if (file.size > 20 * 1024 * 1024) {
|
||||||
throw new Error("file too large")
|
throw new Error("file too large");
|
||||||
}
|
}
|
||||||
onSelection(file)
|
onSelection(file);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
alert(`error: ${(e as any).message}`)
|
alert(`error: ${(e as any).message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute flex w-screen h-screen justify-center items-center pointer-events-none">
|
<div className="absolute flex w-screen h-screen justify-center items-center pointer-events-none">
|
||||||
<label
|
<label
|
||||||
htmlFor={uploadElemId}
|
htmlFor={uploadElemId}
|
||||||
className="grid bg-background border-[2px] border-[dashed] rounded-lg min-w-[600px] hover:bg-primary hover:text-primary-foreground pointer-events-auto"
|
className="grid bg-background border-[2px] border-[dashed] rounded-lg min-w-[600px] hover:bg-primary hover:text-primary-foreground pointer-events-auto"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="grid p-16 w-full h-full"
|
className="grid p-16 w-full h-full"
|
||||||
onDragOver={(ev) => {
|
onDragOver={(ev) => {
|
||||||
ev.stopPropagation()
|
ev.stopPropagation();
|
||||||
ev.preventDefault()
|
ev.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
className="hidden"
|
className="hidden"
|
||||||
id={uploadElemId}
|
id={uploadElemId}
|
||||||
name={uploadElemId}
|
name={uploadElemId}
|
||||||
type="file"
|
type="file"
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
const file = ev.currentTarget.files?.[0]
|
const file = ev.currentTarget.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
onFileSelected(file)
|
onFileSelected(file);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
accept="image/png, image/jpeg"
|
accept="image/png, image/jpeg"
|
||||||
/>
|
/>
|
||||||
<p className="text-center">
|
<p className="text-center">
|
||||||
{resolution === "desktop"
|
{resolution === "desktop"
|
||||||
? "Click here or drag an image file"
|
? "Click here or drag an image file"
|
||||||
: "Tap here to load your picture"}
|
: "Tap here to load your picture"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,198 +1,73 @@
|
||||||
import { PlayIcon } from "@radix-ui/react-icons"
|
import { IconButton, ImageUploadButton } from "@/components/ui/button";
|
||||||
import { useState } from "react"
|
|
||||||
import { IconButton, ImageUploadButton } from "@/components/ui/button"
|
|
||||||
import Shortcuts from "@/components/Shortcuts"
|
|
||||||
import { useImage } from "@/hooks/useImage"
|
|
||||||
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
import PromptInput from "./PromptInput";
|
||||||
import PromptInput from "./PromptInput"
|
import { RotateCw, Image } from "lucide-react";
|
||||||
import { RotateCw, Image, Upload } from "lucide-react"
|
import { useStore } from "@/lib/states";
|
||||||
import FileManager from "./FileManager"
|
|
||||||
import { getMediaFile } from "@/lib/api"
|
|
||||||
import { useStore } from "@/lib/states"
|
|
||||||
import SettingsDialog from "./Settings"
|
|
||||||
import { cn, fileToImage } from "@/lib/utils"
|
|
||||||
import Coffee from "./Coffee"
|
|
||||||
import { useToast } from "./ui/use-toast"
|
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const [
|
const [
|
||||||
file,
|
file,
|
||||||
customMask,
|
isInpainting,
|
||||||
isInpainting,
|
model,
|
||||||
serverConfig,
|
setFile,
|
||||||
runMannually,
|
runInpainting,
|
||||||
enableUploadMask,
|
showPrevMask,
|
||||||
model,
|
hidePrevMask,
|
||||||
setFile,
|
] = useStore((state) => [
|
||||||
setCustomFile,
|
state.file,
|
||||||
runInpainting,
|
state.isInpainting,
|
||||||
showPrevMask,
|
state.settings.model,
|
||||||
hidePrevMask,
|
state.setFile,
|
||||||
imageHeight,
|
state.runInpainting,
|
||||||
imageWidth,
|
state.showPrevMask,
|
||||||
] = useStore((state) => [
|
state.hidePrevMask,
|
||||||
state.file,
|
]);
|
||||||
state.customMask,
|
|
||||||
state.isInpainting,
|
|
||||||
state.serverConfig,
|
|
||||||
state.runMannually(),
|
|
||||||
state.settings.enableUploadMask,
|
|
||||||
state.settings.model,
|
|
||||||
state.setFile,
|
|
||||||
state.setCustomFile,
|
|
||||||
state.runInpainting,
|
|
||||||
state.showPrevMask,
|
|
||||||
state.hidePrevMask,
|
|
||||||
state.imageHeight,
|
|
||||||
state.imageWidth,
|
|
||||||
])
|
|
||||||
|
|
||||||
const { toast } = useToast()
|
const handleRerunLastMask = () => {
|
||||||
const [maskImage, maskImageLoaded] = useImage(customMask)
|
runInpainting();
|
||||||
const [openMaskPopover, setOpenMaskPopover] = useState(false)
|
};
|
||||||
|
|
||||||
const handleRerunLastMask = () => {
|
const onRerunMouseEnter = () => {
|
||||||
runInpainting()
|
showPrevMask();
|
||||||
}
|
};
|
||||||
|
|
||||||
const onRerunMouseEnter = () => {
|
const onRerunMouseLeave = () => {
|
||||||
showPrevMask()
|
hidePrevMask();
|
||||||
}
|
};
|
||||||
|
|
||||||
const onRerunMouseLeave = () => {
|
return (
|
||||||
hidePrevMask()
|
<header className="h-[60px] px-6 py-4 absolute top-[0] flex justify-between items-center w-full z-20 border-b backdrop-filter backdrop-blur-md bg-background/70">
|
||||||
}
|
<div className="flex items-center gap-1">
|
||||||
|
<ImageUploadButton
|
||||||
|
disabled={isInpainting}
|
||||||
|
tooltip="Upload image"
|
||||||
|
onFileUpload={(file) => {
|
||||||
|
setFile(file);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image />
|
||||||
|
</ImageUploadButton>
|
||||||
|
|
||||||
return (
|
{file && !model.need_prompt ? (
|
||||||
<header className="h-[60px] px-6 py-4 absolute top-[0] flex justify-between items-center w-full z-20 border-b backdrop-filter backdrop-blur-md bg-background/70">
|
<IconButton
|
||||||
<div className="flex items-center gap-1">
|
disabled={isInpainting}
|
||||||
{serverConfig.enableFileManager ? (
|
tooltip="Rerun previous mask"
|
||||||
<FileManager
|
onClick={handleRerunLastMask}
|
||||||
photoWidth={512}
|
onMouseEnter={onRerunMouseEnter}
|
||||||
onPhotoClick={async (tab: string, filename: string) => {
|
onMouseLeave={onRerunMouseLeave}
|
||||||
try {
|
>
|
||||||
const newFile = await getMediaFile(tab, filename)
|
<RotateCw />
|
||||||
setFile(newFile)
|
</IconButton>
|
||||||
} catch (e: any) {
|
) : (
|
||||||
toast({
|
<></>
|
||||||
variant: "destructive",
|
)}
|
||||||
description: e.message ? e.message : e.toString(),
|
</div>
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ImageUploadButton
|
{model.need_prompt ? <PromptInput /> : <></>}
|
||||||
disabled={isInpainting}
|
|
||||||
tooltip="Upload image"
|
|
||||||
onFileUpload={(file) => {
|
|
||||||
setFile(file)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image />
|
|
||||||
</ImageUploadButton>
|
|
||||||
|
|
||||||
<div
|
<div className="flex gap-1"></div>
|
||||||
className={cn([
|
</header>
|
||||||
"flex items-center gap-1",
|
);
|
||||||
file && enableUploadMask ? "visible" : "hidden",
|
};
|
||||||
])}
|
|
||||||
>
|
|
||||||
<ImageUploadButton
|
|
||||||
disabled={isInpainting}
|
|
||||||
tooltip="Upload custom mask"
|
|
||||||
onFileUpload={async (file) => {
|
|
||||||
let newCustomMask: HTMLImageElement | null = null
|
|
||||||
try {
|
|
||||||
newCustomMask = await fileToImage(file)
|
|
||||||
} catch (e: any) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
description: e.message ? e.message : e.toString(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
newCustomMask.naturalHeight !== imageHeight ||
|
|
||||||
newCustomMask.naturalWidth !== imageWidth
|
|
||||||
) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
description: `The size of the mask must same as image: ${imageWidth}x${imageHeight}`,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setCustomFile(file)
|
export default Header;
|
||||||
if (!runMannually) {
|
|
||||||
runInpainting()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Upload />
|
|
||||||
</ImageUploadButton>
|
|
||||||
|
|
||||||
{customMask ? (
|
|
||||||
<Popover open={openMaskPopover}>
|
|
||||||
<PopoverTrigger
|
|
||||||
className="btn-primary side-panel-trigger"
|
|
||||||
onMouseEnter={() => setOpenMaskPopover(true)}
|
|
||||||
onMouseLeave={() => setOpenMaskPopover(false)}
|
|
||||||
style={{
|
|
||||||
visibility: customMask ? "visible" : "hidden",
|
|
||||||
outline: "none",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (customMask) {
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton tooltip="Run custom mask">
|
|
||||||
<PlayIcon />
|
|
||||||
</IconButton>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent>
|
|
||||||
{maskImageLoaded ? (
|
|
||||||
<img src={maskImage.src} alt="Custom mask" />
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{file && !model.need_prompt ? (
|
|
||||||
<IconButton
|
|
||||||
disabled={isInpainting}
|
|
||||||
tooltip="Rerun previous mask"
|
|
||||||
onClick={handleRerunLastMask}
|
|
||||||
onMouseEnter={onRerunMouseEnter}
|
|
||||||
onMouseLeave={onRerunMouseLeave}
|
|
||||||
>
|
|
||||||
<RotateCw />
|
|
||||||
</IconButton>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{model.need_prompt ? <PromptInput /> : <></>}
|
|
||||||
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Coffee />
|
|
||||||
<Shortcuts />
|
|
||||||
{serverConfig.disableModelSwitch ? <></> : <SettingsDialog />}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,31 @@
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react";
|
||||||
import Editor from "./Editor"
|
import Editor from "./Editor";
|
||||||
import { currentModel } from "@/lib/api"
|
import { currentModel } from "@/lib/api";
|
||||||
import { useStore } from "@/lib/states"
|
import { useStore } from "@/lib/states";
|
||||||
import ImageSize from "./ImageSize"
|
import ImageSize from "./ImageSize";
|
||||||
import Plugins from "./Plugins"
|
|
||||||
import { InteractiveSeg } from "./InteractiveSeg"
|
|
||||||
import SidePanel from "./SidePanel"
|
|
||||||
import DiffusionProgress from "./DiffusionProgress"
|
|
||||||
|
|
||||||
const Workspace = () => {
|
const Workspace = () => {
|
||||||
const [file, updateSettings] = useStore((state) => [
|
const [file, updateSettings] = useStore((state) => [
|
||||||
state.file,
|
state.file,
|
||||||
state.updateSettings,
|
state.updateSettings,
|
||||||
])
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCurrentModel = async () => {
|
const fetchCurrentModel = async () => {
|
||||||
const model = await currentModel()
|
const model = await currentModel();
|
||||||
updateSettings({ model })
|
updateSettings({ model });
|
||||||
}
|
};
|
||||||
fetchCurrentModel()
|
fetchCurrentModel();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
|
<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
|
||||||
<Plugins />
|
<ImageSize />
|
||||||
<ImageSize />
|
</div>
|
||||||
</div>
|
{file ? <Editor file={file} /> : <></>}
|
||||||
<InteractiveSeg />
|
</>
|
||||||
<DiffusionProgress />
|
);
|
||||||
<SidePanel />
|
};
|
||||||
{file ? <Editor file={file} /> : <></>}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Workspace
|
export default Workspace;
|
||||||
|
|
|
||||||
|
|
@ -1,231 +1,256 @@
|
||||||
import {
|
import {
|
||||||
Filename,
|
Filename,
|
||||||
GenInfo,
|
GenInfo,
|
||||||
ModelInfo,
|
ModelInfo,
|
||||||
PowerPaintTask,
|
PowerPaintTask,
|
||||||
Rect,
|
Rect,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
} from "@/lib/types";
|
} from "@/lib/types";
|
||||||
import { Settings } from "@/lib/states";
|
import { Settings } from "@/lib/states";
|
||||||
import { convertToBase64, srcToFile } from "@/lib/utils";
|
import { convertToBase64, srcToFile } from "@/lib/utils";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export const API_ENDPOINT = import.meta.env.DEV
|
export const API_ENDPOINT = import.meta.env.DEV
|
||||||
? import.meta.env.VITE_BACKEND + "/api/v1"
|
? import.meta.env.VITE_BACKEND + "/api/v1"
|
||||||
: "/api/v1";
|
: "/api/v1";
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: API_ENDPOINT,
|
baseURL: API_ENDPOINT,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default async function inpaint(
|
export default async function inpaint(
|
||||||
imageFile: File,
|
imageFile: File,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
croperRect: Rect,
|
croperRect: Rect,
|
||||||
extenderState: Rect,
|
extenderState: Rect,
|
||||||
mask: File | Blob,
|
mask: File | Blob,
|
||||||
paintByExampleImage: File | null = null
|
paintByExampleImage: File | null = null,
|
||||||
) {
|
) {
|
||||||
const imageBase64 = await convertToBase64(imageFile);
|
const imageBase64 = await convertToBase64(imageFile);
|
||||||
const maskBase64 = await convertToBase64(mask);
|
const maskBase64 = await convertToBase64(mask);
|
||||||
const exampleImageBase64 = paintByExampleImage
|
const exampleImageBase64 = paintByExampleImage
|
||||||
? await convertToBase64(paintByExampleImage)
|
? await convertToBase64(paintByExampleImage)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const res = await fetch(`${API_ENDPOINT}/inpaint`, {
|
const res = await fetch(`${API_ENDPOINT}/inpaint`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
image: imageBase64,
|
image: imageBase64,
|
||||||
mask: maskBase64,
|
mask: maskBase64,
|
||||||
ldm_steps: settings.ldmSteps,
|
ldm_steps: settings.ldmSteps,
|
||||||
ldm_sampler: settings.ldmSampler,
|
ldm_sampler: settings.ldmSampler,
|
||||||
zits_wireframe: settings.zitsWireframe,
|
zits_wireframe: settings.zitsWireframe,
|
||||||
cv2_flag: settings.cv2Flag,
|
cv2_flag: settings.cv2Flag,
|
||||||
cv2_radius: settings.cv2Radius,
|
cv2_radius: settings.cv2Radius,
|
||||||
hd_strategy: "Crop",
|
hd_strategy: "Crop",
|
||||||
hd_strategy_crop_triger_size: 640,
|
hd_strategy_crop_triger_size: 640,
|
||||||
hd_strategy_crop_margin: 128,
|
hd_strategy_crop_margin: 128,
|
||||||
hd_trategy_resize_imit: 2048,
|
hd_trategy_resize_imit: 2048,
|
||||||
prompt: settings.prompt,
|
prompt: settings.prompt,
|
||||||
negative_prompt: settings.negativePrompt,
|
negative_prompt: settings.negativePrompt,
|
||||||
use_croper: settings.showCropper,
|
use_croper: settings.showCropper,
|
||||||
croper_x: croperRect.x,
|
croper_x: croperRect.x,
|
||||||
croper_y: croperRect.y,
|
croper_y: croperRect.y,
|
||||||
croper_height: croperRect.height,
|
croper_height: croperRect.height,
|
||||||
croper_width: croperRect.width,
|
croper_width: croperRect.width,
|
||||||
use_extender: settings.showExtender,
|
use_extender: settings.showExtender,
|
||||||
extender_x: extenderState.x,
|
extender_x: extenderState.x,
|
||||||
extender_y: extenderState.y,
|
extender_y: extenderState.y,
|
||||||
extender_height: extenderState.height,
|
extender_height: extenderState.height,
|
||||||
extender_width: extenderState.width,
|
extender_width: extenderState.width,
|
||||||
sd_mask_blur: settings.sdMaskBlur,
|
sd_mask_blur: settings.sdMaskBlur,
|
||||||
sd_strength: settings.sdStrength,
|
sd_strength: settings.sdStrength,
|
||||||
sd_steps: settings.sdSteps,
|
sd_steps: settings.sdSteps,
|
||||||
sd_guidance_scale: settings.sdGuidanceScale,
|
sd_guidance_scale: settings.sdGuidanceScale,
|
||||||
sd_sampler: settings.sdSampler,
|
sd_sampler: settings.sdSampler,
|
||||||
sd_seed: settings.seedFixed ? settings.seed : -1,
|
sd_seed: settings.seedFixed ? settings.seed : -1,
|
||||||
sd_match_histograms: settings.sdMatchHistograms,
|
sd_match_histograms: settings.sdMatchHistograms,
|
||||||
sd_freeu: settings.enableFreeu,
|
sd_freeu: settings.enableFreeu,
|
||||||
sd_freeu_config: settings.freeuConfig,
|
sd_freeu_config: settings.freeuConfig,
|
||||||
sd_lcm_lora: settings.enableLCMLora,
|
sd_lcm_lora: settings.enableLCMLora,
|
||||||
paint_by_example_example_image: exampleImageBase64,
|
paint_by_example_example_image: exampleImageBase64,
|
||||||
p2p_image_guidance_scale: settings.p2pImageGuidanceScale,
|
p2p_image_guidance_scale: settings.p2pImageGuidanceScale,
|
||||||
enable_controlnet: settings.enableControlnet,
|
enable_controlnet: settings.enableControlnet,
|
||||||
controlnet_conditioning_scale: settings.controlnetConditioningScale,
|
controlnet_conditioning_scale: settings.controlnetConditioningScale,
|
||||||
controlnet_method: settings.controlnetMethod
|
controlnet_method: settings.controlnetMethod
|
||||||
? settings.controlnetMethod
|
? settings.controlnetMethod
|
||||||
: "",
|
: "",
|
||||||
powerpaint_task: settings.showExtender
|
powerpaint_task: settings.showExtender
|
||||||
? PowerPaintTask.outpainting
|
? PowerPaintTask.outpainting
|
||||||
: settings.powerpaintTask,
|
: settings.powerpaintTask,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
return {
|
return {
|
||||||
blob: URL.createObjectURL(blob),
|
blob: URL.createObjectURL(blob),
|
||||||
seed: res.headers.get("X-Seed"),
|
seed: res.headers.get("X-Seed"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const errors = await res.json();
|
const errors = await res.json();
|
||||||
throw new Error(`Something went wrong: ${errors.errors}`);
|
throw new Error(`Something went wrong: ${errors.errors}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerConfig(): Promise<ServerConfig> {
|
export async function getServerConfig(): Promise<ServerConfig> {
|
||||||
const res = await api.get(`/server-config`);
|
const res = await api.get(`/server-config`);
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function switchModel(name: string): Promise<ModelInfo> {
|
export async function switchModel(name: string): Promise<ModelInfo> {
|
||||||
const res = await api.post(`/model`, { name });
|
const res = await api.post(`/model`, { name });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function switchPluginModel(
|
export async function switchPluginModel(
|
||||||
plugin_name: string,
|
plugin_name: string,
|
||||||
model_name: string
|
model_name: string,
|
||||||
) {
|
) {
|
||||||
return api.post(`/switch_plugin_model`, { plugin_name, model_name });
|
return api.post(`/switch_plugin_model`, { plugin_name, model_name });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function currentModel(): Promise<ModelInfo> {
|
export async function currentModel(): Promise<ModelInfo> {
|
||||||
const res = await api.get("/model");
|
const res = await api.get("/model");
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runPlugin(
|
export async function runPlugin(
|
||||||
genMask: boolean,
|
genMask: boolean,
|
||||||
name: string,
|
name: string,
|
||||||
imageFile: File,
|
imageFile: File,
|
||||||
upscale?: number,
|
upscale?: number,
|
||||||
clicks?: number[][]
|
clicks?: number[][],
|
||||||
) {
|
) {
|
||||||
const imageBase64 = await convertToBase64(imageFile);
|
const imageBase64 = await convertToBase64(imageFile);
|
||||||
const p = genMask ? "run_plugin_gen_mask" : "run_plugin_gen_image";
|
const p = genMask ? "run_plugin_gen_mask" : "run_plugin_gen_image";
|
||||||
const res = await fetch(`${API_ENDPOINT}/${p}`, {
|
const res = await fetch(`${API_ENDPOINT}/${p}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name,
|
name,
|
||||||
image: imageBase64,
|
image: imageBase64,
|
||||||
upscale,
|
upscale,
|
||||||
clicks,
|
clicks,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
return { blob: URL.createObjectURL(blob) };
|
return { blob: URL.createObjectURL(blob) };
|
||||||
}
|
}
|
||||||
const errMsg = await res.json();
|
const errMsg = await res.json();
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMediaFile(tab: string, filename: string) {
|
export async function getMediaFile(tab: string, filename: string) {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${API_ENDPOINT}/media_file?tab=${tab}&filename=${encodeURIComponent(
|
`${API_ENDPOINT}/media_file?tab=${tab}&filename=${encodeURIComponent(
|
||||||
filename
|
filename,
|
||||||
)}`,
|
)}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
const file = new File([blob], filename, {
|
const file = new File([blob], filename, {
|
||||||
type: res.headers.get("Content-Type") ?? "image/png",
|
type: res.headers.get("Content-Type") ?? "image/png",
|
||||||
});
|
});
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
const errMsg = await res.json();
|
const errMsg = await res.json();
|
||||||
throw new Error(errMsg.errors);
|
throw new Error(errMsg.errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMedias(tab: string): Promise<Filename[]> {
|
export async function getMedias(tab: string): Promise<Filename[]> {
|
||||||
const res = await api.get(`medias`, { params: { tab } });
|
const res = await api.get(`medias`, { params: { tab } });
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadToOutput(
|
export async function downloadToOutput(
|
||||||
image: HTMLImageElement,
|
image: HTMLImageElement,
|
||||||
filename: string,
|
filename: string,
|
||||||
mimeType: string
|
mimeType: string,
|
||||||
) {
|
) {
|
||||||
const file = await srcToFile(image.src, filename, mimeType);
|
const file = await srcToFile(image.src, filename, mimeType);
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append("file", file);
|
fd.append("file", file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_ENDPOINT}/save_image`, {
|
const res = await fetch(`${API_ENDPOINT}/save_image`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: fd,
|
body: fd,
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const errMsg = await res.text();
|
const errMsg = await res.text();
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Something went wrong: ${error}`);
|
throw new Error(`Something went wrong: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGenInfo(file: File): Promise<GenInfo> {
|
export async function getGenInfo(file: File): Promise<GenInfo> {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append("file", file);
|
fd.append("file", file);
|
||||||
const res = await api.post(`/gen-info`, fd);
|
const res = await api.post(`/gen-info`, fd);
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSamplers(): Promise<string[]> {
|
export async function getSamplers(): Promise<string[]> {
|
||||||
const res = await api.post("/samplers");
|
const res = await api.post("/samplers");
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postAdjustMask(
|
export async function postAdjustMask(
|
||||||
mask: File | Blob,
|
mask: File | Blob,
|
||||||
operate: "expand" | "shrink" | "reverse",
|
operate: "expand" | "shrink" | "reverse",
|
||||||
kernel_size: number
|
kernel_size: number,
|
||||||
) {
|
) {
|
||||||
const maskBase64 = await convertToBase64(mask);
|
const maskBase64 = await convertToBase64(mask);
|
||||||
const res = await fetch(`${API_ENDPOINT}/adjust_mask`, {
|
const res = await fetch(`${API_ENDPOINT}/adjust_mask`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
mask: maskBase64,
|
mask: maskBase64,
|
||||||
operate: operate,
|
operate: operate,
|
||||||
kernel_size: kernel_size,
|
kernel_size: kernel_size,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const blob = await res.blob();
|
const blob = await res.blob();
|
||||||
return blob;
|
return blob;
|
||||||
}
|
}
|
||||||
const errMsg = await res.json();
|
const errMsg = await res.json();
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitMask(imageFile: File, mask: File | Blob) {
|
||||||
|
const imageBase64 = await convertToBase64(imageFile);
|
||||||
|
const maskBase64 = await convertToBase64(mask);
|
||||||
|
|
||||||
|
const res = await fetch(`${API_ENDPOINT}/submit-mask`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
image: imageBase64,
|
||||||
|
mask: maskBase64,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const blob = await res.blob();
|
||||||
|
return {
|
||||||
|
blob: URL.createObjectURL(blob),
|
||||||
|
seed: res.headers.get("X-Seed"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// const errors = await res.json();
|
||||||
|
throw new Error(`Submit successfull.`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue