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