Merge pull request 'update FE' (#2) from vi into main

Reviewed-on: #2
This commit is contained in:
hoangvi.ng 2025-07-30 12:35:25 +10:00
commit d30a97e317
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 { 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

View File

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

View File

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

View File

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

View File

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