update FE

This commit is contained in:
dbdbd9 2025-07-30 09:33:40 +07:00
parent 5d726d51ab
commit ee3809670b
7 changed files with 2709 additions and 2625 deletions

View File

@ -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

View File

@ -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>
) );
} }

View File

@ -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

View File

@ -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;

View File

@ -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