new web init

This commit is contained in:
Qing
2023-11-22 08:53:20 +08:00
parent a5c241ac02
commit 04c5dfece8
51 changed files with 11603 additions and 0 deletions

270
web_app/src/lib/api.ts Normal file
View File

@@ -0,0 +1,270 @@
import { PluginName } from "@/lib/types"
import { ControlNetMethodMap, Rect, Settings } from "@/lib/store"
import { dataURItoBlob, loadImage, srcToFile } from "@/lib/utils"
export const API_ENDPOINT = import.meta.env.VITE_BACKEND
? import.meta.env.VITE_BACKEND
: ""
export default async function inpaint(
imageFile: File,
settings: Settings,
croperRect: Rect,
prompt?: string,
negativePrompt?: string,
seed?: number,
maskBase64?: string,
customMask?: File,
paintByExampleImage?: File
) {
// 1080, 2000, Original
const fd = new FormData()
fd.append("image", imageFile)
if (maskBase64 !== undefined) {
fd.append("mask", dataURItoBlob(maskBase64))
} else if (customMask !== undefined) {
fd.append("mask", customMask)
}
const hdSettings = settings.hdSettings[settings.model]
fd.append("ldmSteps", settings.ldmSteps.toString())
fd.append("ldmSampler", settings.ldmSampler.toString())
fd.append("zitsWireframe", settings.zitsWireframe.toString())
fd.append("hdStrategy", hdSettings.hdStrategy)
fd.append("hdStrategyCropMargin", hdSettings.hdStrategyCropMargin.toString())
fd.append(
"hdStrategyCropTrigerSize",
hdSettings.hdStrategyCropTrigerSize.toString()
)
fd.append(
"hdStrategyResizeLimit",
hdSettings.hdStrategyResizeLimit.toString()
)
fd.append("prompt", prompt === undefined ? "" : prompt)
fd.append(
"negativePrompt",
negativePrompt === undefined ? "" : negativePrompt
)
fd.append("croperX", croperRect.x.toString())
fd.append("croperY", croperRect.y.toString())
fd.append("croperHeight", croperRect.height.toString())
fd.append("croperWidth", croperRect.width.toString())
fd.append("useCroper", settings.showCroper ? "true" : "false")
fd.append("sdMaskBlur", settings.sdMaskBlur.toString())
fd.append("sdStrength", settings.sdStrength.toString())
fd.append("sdSteps", settings.sdSteps.toString())
fd.append("sdGuidanceScale", settings.sdGuidanceScale.toString())
fd.append("sdSampler", settings.sdSampler.toString())
fd.append("sdSeed", seed ? seed.toString() : "-1")
fd.append("sdMatchHistograms", settings.sdMatchHistograms ? "true" : "false")
fd.append("sdScale", (settings.sdScale / 100).toString())
fd.append("cv2Radius", settings.cv2Radius.toString())
fd.append("cv2Flag", settings.cv2Flag.toString())
fd.append("paintByExampleSteps", settings.paintByExampleSteps.toString())
fd.append(
"paintByExampleGuidanceScale",
settings.paintByExampleGuidanceScale.toString()
)
fd.append("paintByExampleSeed", seed ? seed.toString() : "-1")
fd.append(
"paintByExampleMaskBlur",
settings.paintByExampleMaskBlur.toString()
)
fd.append(
"paintByExampleMatchHistograms",
settings.paintByExampleMatchHistograms ? "true" : "false"
)
// TODO: resize image's shortest_edge to 224 before pass to backend, save network time?
// https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPImageProcessor
if (paintByExampleImage) {
fd.append("paintByExampleImage", paintByExampleImage)
}
// InstructPix2Pix
fd.append("p2pSteps", settings.p2pSteps.toString())
fd.append("p2pImageGuidanceScale", settings.p2pImageGuidanceScale.toString())
fd.append("p2pGuidanceScale", settings.p2pGuidanceScale.toString())
// ControlNet
fd.append(
"controlnet_conditioning_scale",
settings.controlnetConditioningScale.toString()
)
fd.append(
"controlnet_method",
ControlNetMethodMap[settings.controlnetMethod.toString()]
)
try {
const res = await fetch(`${API_ENDPOINT}/inpaint`, {
method: "POST",
body: fd,
})
if (res.ok) {
const blob = await res.blob()
const newSeed = res.headers.get("x-seed")
return { blob: URL.createObjectURL(blob), seed: newSeed }
}
const errMsg = await res.text()
throw new Error(errMsg)
} catch (error) {
throw new Error(`Something went wrong: ${error}`)
}
}
export function getServerConfig() {
return fetch(`${API_ENDPOINT}/server_config`, {
method: "GET",
})
}
export function switchModel(name: string) {
const fd = new FormData()
fd.append("name", name)
return fetch(`${API_ENDPOINT}/model`, {
method: "POST",
body: fd,
})
}
export function currentModel() {
return fetch(`${API_ENDPOINT}/model`, {
method: "GET",
})
}
export function isDesktop() {
return fetch(`${API_ENDPOINT}/is_desktop`, {
method: "GET",
})
}
export function modelDownloaded(name: string) {
return fetch(`${API_ENDPOINT}/model_downloaded/${name}`, {
method: "GET",
})
}
export async function runPlugin(
name: string,
imageFile: File,
upscale?: number,
maskFile?: File | null,
clicks?: number[][]
) {
const fd = new FormData()
fd.append("name", name)
fd.append("image", imageFile)
if (upscale) {
fd.append("upscale", upscale.toString())
}
if (clicks) {
fd.append("clicks", JSON.stringify(clicks))
}
if (maskFile) {
fd.append("mask", maskFile)
}
try {
const res = await fetch(`${API_ENDPOINT}/run_plugin`, {
method: "POST",
body: fd,
})
if (res.ok) {
const blob = await res.blob()
return { blob: URL.createObjectURL(blob) }
}
const errMsg = await res.text()
throw new Error(errMsg)
} catch (error) {
throw new Error(`Something went wrong: ${error}`)
}
}
export async function getMediaFile(tab: string, filename: string) {
const res = await fetch(
`${API_ENDPOINT}/media/${tab}/${encodeURIComponent(filename)}`,
{
method: "GET",
}
)
if (res.ok) {
const blob = await res.blob()
const file = new File([blob], filename)
return file
}
const errMsg = await res.text()
throw new Error(errMsg)
}
export async function getMedias(tab: string) {
const res = await fetch(`${API_ENDPOINT}/medias/${tab}`, {
method: "GET",
})
if (res.ok) {
const filenames = await res.json()
return filenames
}
const errMsg = await res.text()
throw new Error(errMsg)
}
export async function downloadToOutput(
image: HTMLImageElement,
filename: string,
mimeType: string
) {
const file = await srcToFile(image.src, filename, mimeType)
const fd = new FormData()
fd.append("image", file)
fd.append("filename", filename)
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 makeGif(
originFile: File,
cleanImage: HTMLImageElement,
filename: string,
mimeType: string
) {
const cleanFile = await srcToFile(cleanImage.src, filename, mimeType)
const fd = new FormData()
fd.append("name", PluginName.MakeGIF)
fd.append("image", originFile)
fd.append("clean_img", cleanFile)
fd.append("filename", filename)
try {
const res = await fetch(`${API_ENDPOINT}/run_plugin`, {
method: "POST",
body: fd,
})
if (!res.ok) {
const errMsg = await res.text()
throw new Error(errMsg)
}
const blob = await res.blob()
const newImage = new Image()
await loadImage(newImage, URL.createObjectURL(blob))
return newImage
} catch (error) {
throw new Error(`Something went wrong: ${error}`)
}
}

22
web_app/src/lib/event.ts Normal file
View File

@@ -0,0 +1,22 @@
import mitt from "mitt"
export const EVENT_PROMPT = "prompt"
export const EVENT_CUSTOM_MASK = "custom_mask"
export interface CustomMaskEventData {
mask: File
}
export const EVENT_PAINT_BY_EXAMPLE = "paint_by_example"
export interface PaintByExampleEventData {
image: File
}
export const RERUN_LAST_MASK = "rerun_last_mask"
export const DREAM_BUTTON_MOUSE_ENTER = "dream_button_mouse_enter"
export const DREAM_BUTTON_MOUSE_LEAVE = "dream_btoon_mouse_leave"
const emitter = mitt()
export default emitter

902
web_app/src/lib/store.ts Normal file
View File

@@ -0,0 +1,902 @@
import { atom, selector } from "recoil"
import _ from "lodash"
export enum HDStrategy {
ORIGINAL = "Original",
RESIZE = "Resize",
CROP = "Crop",
}
export enum LDMSampler {
ddim = "ddim",
plms = "plms",
}
function strEnum<T extends string>(o: Array<T>): { [K in T]: K } {
return o.reduce((res, key) => {
res[key] = key
return res
}, Object.create(null))
}
export enum AIModel {
LAMA = "lama",
LDM = "ldm",
ZITS = "zits",
MAT = "mat",
FCF = "fcf",
SD15 = "sd1.5",
ANYTHING4 = "anything4",
REALISTIC_VISION_1_4 = "realisticVision1.4",
SD2 = "sd2",
CV2 = "cv2",
Mange = "manga",
PAINT_BY_EXAMPLE = "paint_by_example",
PIX2PIX = "instruct_pix2pix",
KANDINSKY22 = "kandinsky2.2",
}
export enum ControlNetMethod {
canny = "canny",
inpaint = "inpaint",
openpose = "openpose",
depth = "depth",
}
export const ControlNetMethodMap: any = {
canny: "control_v11p_sd15_canny",
inpaint: "control_v11p_sd15_inpaint",
openpose: "control_v11p_sd15_openpose",
depth: "control_v11f1p_sd15_depth",
}
export const ControlNetMethodMap2: any = {
control_v11p_sd15_canny: "canny",
control_v11p_sd15_inpaint: "inpaint",
control_v11p_sd15_openpose: "openpose",
control_v11f1p_sd15_depth: "depth",
}
export const maskState = atom<File | undefined>({
key: "maskState",
default: undefined,
})
export const paintByExampleImageState = atom<File | undefined>({
key: "paintByExampleImageState",
default: undefined,
})
export interface Rect {
x: number
y: number
width: number
height: number
}
interface AppState {
file: File | undefined
imageHeight: number
imageWidth: number
disableShortCuts: boolean
isInpainting: boolean
isDisableModelSwitch: boolean
isEnableAutoSaving: boolean
isInteractiveSeg: boolean
isInteractiveSegRunning: boolean
interactiveSegClicks: number[][]
enableFileManager: boolean
gifImage: HTMLImageElement | undefined
brushSize: number
isControlNet: boolean
controlNetMethod: string
plugins: string[]
isPluginRunning: boolean
}
export const appState = atom<AppState>({
key: "appState",
default: {
file: undefined,
imageHeight: 0,
imageWidth: 0,
disableShortCuts: false,
isInpainting: false,
isDisableModelSwitch: false,
isEnableAutoSaving: false,
isInteractiveSeg: false,
isInteractiveSegRunning: false,
interactiveSegClicks: [],
enableFileManager: false,
gifImage: undefined,
brushSize: 40,
isControlNet: false,
controlNetMethod: ControlNetMethod.canny,
plugins: [],
isPluginRunning: false,
},
})
export const propmtState = atom<string>({
key: "promptState",
default: "",
})
export const negativePropmtState = atom<string>({
key: "negativePromptState",
default: "",
})
export const isInpaintingState = selector({
key: "isInpainting",
get: ({ get }) => {
const app = get(appState)
return app.isInpainting
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isInpainting: newValue })
},
})
export const isPluginRunningState = selector({
key: "isPluginRunningState",
get: ({ get }) => {
const app = get(appState)
return app.isPluginRunning
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isPluginRunning: newValue })
},
})
export const serverConfigState = selector({
key: "serverConfigState",
get: ({ get }) => {
const app = get(appState)
return {
isControlNet: app.isControlNet,
controlNetMethod: app.controlNetMethod,
isDisableModelSwitchState: app.isDisableModelSwitch,
isEnableAutoSaving: app.isEnableAutoSaving,
enableFileManager: app.enableFileManager,
plugins: app.plugins,
}
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
const methodShortName = ControlNetMethodMap2[newValue.controlNetMethod]
set(appState, { ...app, ...newValue, controlnetMethod: methodShortName })
const setting = get(settingState)
set(settingState, {
...setting,
controlnetMethod: methodShortName,
})
},
})
export const brushSizeState = selector({
key: "brushSizeState",
get: ({ get }) => {
const app = get(appState)
return app.brushSize
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, brushSize: newValue })
},
})
export const imageHeightState = selector({
key: "imageHeightState",
get: ({ get }) => {
const app = get(appState)
return app.imageHeight
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, imageHeight: newValue })
},
})
export const imageWidthState = selector({
key: "imageWidthState",
get: ({ get }) => {
const app = get(appState)
return app.imageWidth
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, imageWidth: newValue })
},
})
export const enableFileManagerState = selector({
key: "enableFileManagerState",
get: ({ get }) => {
const app = get(appState)
return app.enableFileManager
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, enableFileManager: newValue })
},
})
export const gifImageState = selector({
key: "gifImageState",
get: ({ get }) => {
const app = get(appState)
return app.gifImage
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, gifImage: newValue })
},
})
export const fileState = selector({
key: "fileState",
get: ({ get }) => {
const app = get(appState)
return app.file
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, {
...app,
file: newValue,
interactiveSegClicks: [],
isInteractiveSeg: false,
isInteractiveSegRunning: false,
})
const setting = get(settingState)
set(settingState, {
...setting,
sdScale: 100,
})
},
})
export const isInteractiveSegState = selector({
key: "isInteractiveSegState",
get: ({ get }) => {
const app = get(appState)
return app.isInteractiveSeg
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isInteractiveSeg: newValue })
},
})
export const isInteractiveSegRunningState = selector({
key: "isInteractiveSegRunningState",
get: ({ get }) => {
const app = get(appState)
return app.isInteractiveSegRunning
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isInteractiveSegRunning: newValue })
},
})
export const isProcessingState = selector({
key: "isProcessingState",
get: ({ get }) => {
const app = get(appState)
return (
app.isInteractiveSegRunning || app.isPluginRunning || app.isInpainting
)
},
})
export const interactiveSegClicksState = selector({
key: "interactiveSegClicksState",
get: ({ get }) => {
const app = get(appState)
return app.interactiveSegClicks
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, interactiveSegClicks: newValue })
},
})
export const isDisableModelSwitchState = selector({
key: "isDisableModelSwitchState",
get: ({ get }) => {
const app = get(appState)
return app.isDisableModelSwitch
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isDisableModelSwitch: newValue })
},
})
export const isControlNetState = selector({
key: "isControlNetState",
get: ({ get }) => {
const app = get(appState)
return app.isControlNet
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isControlNet: newValue })
},
})
export const isEnableAutoSavingState = selector({
key: "isEnableAutoSavingState",
get: ({ get }) => {
const app = get(appState)
return app.isEnableAutoSaving
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isEnableAutoSaving: newValue })
},
})
export const croperState = atom<Rect>({
key: "croperState",
default: {
x: 0,
y: 0,
width: 512,
height: 512,
},
})
export const SIDE_PANEL_TAB = strEnum(["inpainting", "outpainting"])
export type SIDE_PANEL_TAB_TYPE = keyof typeof SIDE_PANEL_TAB
export interface SidePanelState {
tab: SIDE_PANEL_TAB_TYPE
}
export const sidePanelTabState = atom<SidePanelState>({
key: "sidePanelTabState",
default: {
tab: SIDE_PANEL_TAB.inpainting,
},
})
export const croperX = selector({
key: "croperX",
get: ({ get }) => get(croperState).x,
set: ({ get, set }, newValue: any) => {
const rect = get(croperState)
set(croperState, { ...rect, x: newValue })
},
})
export const croperY = selector({
key: "croperY",
get: ({ get }) => get(croperState).y,
set: ({ get, set }, newValue: any) => {
const rect = get(croperState)
set(croperState, { ...rect, y: newValue })
},
})
export const croperHeight = selector({
key: "croperHeight",
get: ({ get }) => get(croperState).height,
set: ({ get, set }, newValue: any) => {
const rect = get(croperState)
set(croperState, { ...rect, height: newValue })
},
})
export const croperWidth = selector({
key: "croperWidth",
get: ({ get }) => get(croperState).width,
set: ({ get, set }, newValue: any) => {
const rect = get(croperState)
set(croperState, { ...rect, width: newValue })
},
})
export const extenderState = atom<Rect>({
key: "extenderState",
default: {
x: 0,
y: 0,
width: 512,
height: 512,
},
})
export const extenderX = selector({
key: "extenderX",
get: ({ get }) => get(extenderState).x,
set: ({ get, set }, newValue: any) => {
const rect = get(extenderState)
set(extenderState, { ...rect, x: newValue })
},
})
export const extenderY = selector({
key: "extenderY",
get: ({ get }) => get(extenderState).y,
set: ({ get, set }, newValue: any) => {
const rect = get(extenderState)
set(extenderState, { ...rect, y: newValue })
},
})
export const extenderHeight = selector({
key: "extenderHeight",
get: ({ get }) => get(extenderState).height,
set: ({ get, set }, newValue: any) => {
const rect = get(extenderState)
set(extenderState, { ...rect, height: newValue })
},
})
export const extenderWidth = selector({
key: "extenderWidth",
get: ({ get }) => get(extenderState).width,
set: ({ get, set }, newValue: any) => {
const rect = get(extenderState)
set(extenderState, { ...rect, width: newValue })
},
})
interface ToastAtomState {
open: boolean
desc: string
state: ToastState
duration: number
}
export const toastState = atom<ToastAtomState>({
key: "toastState",
default: {
open: false,
desc: "",
state: "default",
duration: 3000,
},
})
export const shortcutsState = atom<boolean>({
key: "shortcutsState",
default: false,
})
export interface HDSettings {
hdStrategy: HDStrategy
hdStrategyResizeLimit: number
hdStrategyCropTrigerSize: number
hdStrategyCropMargin: number
enabled: boolean
}
type ModelsHDSettings = { [key in AIModel]: HDSettings }
export enum CV2Flag {
INPAINT_NS = "INPAINT_NS",
INPAINT_TELEA = "INPAINT_TELEA",
}
export interface Settings {
show: boolean
showCroper: boolean
downloadMask: boolean
graduallyInpainting: boolean
runInpaintingManually: boolean
model: AIModel
hdSettings: ModelsHDSettings
// For LDM
ldmSteps: number
ldmSampler: LDMSampler
// For ZITS
zitsWireframe: boolean
// For SD
sdMaskBlur: number
sdMode: SDMode
sdStrength: number
sdSteps: number
sdGuidanceScale: number
sdSampler: SDSampler
sdSeed: number
sdSeedFixed: boolean // true: use sdSeed, false: random generate seed on backend
sdNumSamples: number
sdMatchHistograms: boolean
sdScale: number
// For OpenCV2
cv2Radius: number
cv2Flag: CV2Flag
// Paint by Example
paintByExampleSteps: number
paintByExampleGuidanceScale: number
paintByExampleSeed: number
paintByExampleSeedFixed: boolean
paintByExampleMaskBlur: number
paintByExampleMatchHistograms: boolean
// InstructPix2Pix
p2pSteps: number
p2pImageGuidanceScale: number
p2pGuidanceScale: number
// ControlNet
controlnetConditioningScale: number
controlnetMethod: string
}
const defaultHDSettings: ModelsHDSettings = {
[AIModel.LAMA]: {
hdStrategy: HDStrategy.CROP,
hdStrategyResizeLimit: 2048,
hdStrategyCropTrigerSize: 800,
hdStrategyCropMargin: 196,
enabled: true,
},
[AIModel.LDM]: {
hdStrategy: HDStrategy.CROP,
hdStrategyResizeLimit: 1080,
hdStrategyCropTrigerSize: 1080,
hdStrategyCropMargin: 128,
enabled: true,
},
[AIModel.ZITS]: {
hdStrategy: HDStrategy.CROP,
hdStrategyResizeLimit: 1024,
hdStrategyCropTrigerSize: 1024,
hdStrategyCropMargin: 128,
enabled: true,
},
[AIModel.MAT]: {
hdStrategy: HDStrategy.CROP,
hdStrategyResizeLimit: 1024,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: true,
},
[AIModel.FCF]: {
hdStrategy: HDStrategy.CROP,
hdStrategyResizeLimit: 512,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.SD15]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.ANYTHING4]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.REALISTIC_VISION_1_4]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.SD2]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.PAINT_BY_EXAMPLE]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.PIX2PIX]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
[AIModel.Mange]: {
hdStrategy: HDStrategy.CROP,
hdStrategyResizeLimit: 1280,
hdStrategyCropTrigerSize: 1024,
hdStrategyCropMargin: 196,
enabled: true,
},
[AIModel.CV2]: {
hdStrategy: HDStrategy.RESIZE,
hdStrategyResizeLimit: 1080,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: true,
},
[AIModel.KANDINSKY22]: {
hdStrategy: HDStrategy.ORIGINAL,
hdStrategyResizeLimit: 768,
hdStrategyCropTrigerSize: 512,
hdStrategyCropMargin: 128,
enabled: false,
},
}
export enum SDSampler {
ddim = "ddim",
pndm = "pndm",
klms = "k_lms",
kEuler = "k_euler",
kEulerA = "k_euler_a",
dpmPlusPlus = "dpm++",
uni_pc = "uni_pc",
}
export enum SDMode {
text2img = "text2img",
img2img = "img2img",
inpainting = "inpainting",
}
export const settingStateDefault: Settings = {
show: false,
showCroper: false,
downloadMask: false,
graduallyInpainting: true,
runInpaintingManually: false,
model: AIModel.LAMA,
hdSettings: defaultHDSettings,
ldmSteps: 25,
ldmSampler: LDMSampler.plms,
zitsWireframe: true,
// SD
sdMaskBlur: 5,
sdMode: SDMode.inpainting,
sdStrength: 0.75,
sdSteps: 50,
sdGuidanceScale: 7.5,
sdSampler: SDSampler.uni_pc,
sdSeed: 42,
sdSeedFixed: false,
sdNumSamples: 1,
sdMatchHistograms: false,
sdScale: 100,
// CV2
cv2Radius: 5,
cv2Flag: CV2Flag.INPAINT_NS,
// Paint by Example
paintByExampleSteps: 50,
paintByExampleGuidanceScale: 7.5,
paintByExampleSeed: 42,
paintByExampleMaskBlur: 5,
paintByExampleSeedFixed: false,
paintByExampleMatchHistograms: false,
// InstructPix2Pix
p2pSteps: 50,
p2pImageGuidanceScale: 1.5,
p2pGuidanceScale: 7.5,
// ControlNet
controlnetConditioningScale: 0.4,
controlnetMethod: ControlNetMethod.canny,
}
const localStorageEffect =
(key: string) =>
({ setSelf, onSet }: any) => {
const savedValue = localStorage.getItem(key)
if (savedValue != null) {
const storageSettings = JSON.parse(savedValue)
storageSettings.show = false
const restored = _.merge(
_.cloneDeep(settingStateDefault),
storageSettings
)
setSelf(restored)
}
onSet((newValue: Settings, val: string, isReset: boolean) =>
isReset
? localStorage.removeItem(key)
: localStorage.setItem(key, JSON.stringify(newValue))
)
}
const ROOT_STATE_KEY = "settingsState4"
// Each atom can reference an array of these atom effect functions which are called in priority order when the atom is initialized
// https://recoiljs.org/docs/guides/atom-effects/#local-storage-persistence
export const settingState = atom<Settings>({
key: ROOT_STATE_KEY,
default: settingStateDefault,
effects: [localStorageEffect(ROOT_STATE_KEY)],
})
export const seedState = selector({
key: "seed",
get: ({ get }) => {
const settings = get(settingState)
switch (settings.model) {
case AIModel.PAINT_BY_EXAMPLE:
return settings.paintByExampleSeedFixed
? settings.paintByExampleSeed
: -1
default:
return settings.sdSeedFixed ? settings.sdSeed : -1
}
},
set: ({ get, set }, newValue: any) => {
const settings = get(settingState)
switch (settings.model) {
case AIModel.PAINT_BY_EXAMPLE:
if (!settings.paintByExampleSeedFixed) {
set(settingState, { ...settings, paintByExampleSeed: newValue })
}
break
default:
if (!settings.sdSeedFixed) {
set(settingState, { ...settings, sdSeed: newValue })
}
}
},
})
export const hdSettingsState = selector({
key: "hdSettings",
get: ({ get }) => {
const settings = get(settingState)
return settings.hdSettings[settings.model]
},
set: ({ get, set }, newValue: any) => {
const settings = get(settingState)
const hdSettings = settings.hdSettings[settings.model]
const newHDSettings = { ...hdSettings, ...newValue }
set(settingState, {
...settings,
hdSettings: { ...settings.hdSettings, [settings.model]: newHDSettings },
})
},
})
export const isSDState = selector({
key: "isSD",
get: ({ get }) => {
const settings = get(settingState)
return (
settings.model === AIModel.SD15 ||
settings.model === AIModel.SD2 ||
settings.model === AIModel.ANYTHING4 ||
settings.model === AIModel.REALISTIC_VISION_1_4 ||
settings.model === AIModel.KANDINSKY22
)
},
})
export const isPaintByExampleState = selector({
key: "isPaintByExampleState",
get: ({ get }) => {
const settings = get(settingState)
return settings.model === AIModel.PAINT_BY_EXAMPLE
},
})
export const isPix2PixState = selector({
key: "isPix2PixState",
get: ({ get }) => {
const settings = get(settingState)
return settings.model === AIModel.PIX2PIX
},
})
export const runManuallyState = selector({
key: "runManuallyState",
get: ({ get }) => {
const settings = get(settingState)
const isSD = get(isSDState)
const isPaintByExample = get(isPaintByExampleState)
const isPix2Pix = get(isPix2PixState)
return (
settings.runInpaintingManually || isSD || isPaintByExample || isPix2Pix
)
},
})
export const isDiffusionModelsState = selector({
key: "isDiffusionModelsState",
get: ({ get }) => {
const isSD = get(isSDState)
const isPaintByExample = get(isPaintByExampleState)
const isPix2Pix = get(isPix2PixState)
return isSD || isPaintByExample || isPix2Pix
},
})
export enum SortBy {
NAME = "name",
CTIME = "ctime",
MTIME = "mtime",
}
export enum SortOrder {
DESCENDING = "desc",
ASCENDING = "asc",
}
interface FileManagerState {
sortBy: SortBy
sortOrder: SortOrder
layout: "rows" | "masonry"
searchText: string
}
const FILE_MANAGER_STATE_KEY = "fileManagerState"
export const fileManagerState = atom<FileManagerState>({
key: FILE_MANAGER_STATE_KEY,
default: {
sortBy: SortBy.CTIME,
sortOrder: SortOrder.DESCENDING,
layout: "masonry",
searchText: "",
},
effects: [localStorageEffect(FILE_MANAGER_STATE_KEY)],
})
export const fileManagerSortBy = selector({
key: "fileManagerSortBy",
get: ({ get }) => get(fileManagerState).sortBy,
set: ({ get, set }, newValue: any) => {
const val = get(fileManagerState)
set(fileManagerState, { ...val, sortBy: newValue })
},
})
export const fileManagerSortOrder = selector({
key: "fileManagerSortOrder",
get: ({ get }) => get(fileManagerState).sortOrder,
set: ({ get, set }, newValue: any) => {
const val = get(fileManagerState)
set(fileManagerState, { ...val, sortOrder: newValue })
},
})
export const fileManagerLayout = selector({
key: "fileManagerLayout",
get: ({ get }) => get(fileManagerState).layout,
set: ({ get, set }, newValue: any) => {
const val = get(fileManagerState)
set(fileManagerState, { ...val, layout: newValue })
},
})
export const fileManagerSearchText = selector({
key: "fileManagerSearchText",
get: ({ get }) => get(fileManagerState).searchText,
set: ({ get, set }, newValue: any) => {
const val = get(fileManagerState)
set(fileManagerState, { ...val, searchText: newValue })
},
})

9
web_app/src/lib/types.ts Normal file
View File

@@ -0,0 +1,9 @@
export enum PluginName {
RemoveBG = "RemoveBG",
AnimeSeg = "AnimeSeg",
RealESRGAN = "RealESRGAN",
GFPGAN = "GFPGAN",
RestoreFormer = "RestoreFormer",
InteractiveSeg = "InteractiveSeg",
MakeGIF = "MakeGIF",
}

133
web_app/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,133 @@
import { type ClassValue, clsx } from "clsx"
import { SyntheticEvent } from "react"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function keepGUIAlive() {
async function getRequest(url = "") {
const response = await fetch(url, {
method: "GET",
cache: "no-cache",
})
return response.json()
}
const keepAliveServer = () => {
const url = document.location
const route = "/flaskwebgui-keep-server-alive"
getRequest(url + route).then((data) => {
return data
})
}
const intervalRequest = 3 * 1000
keepAliveServer()
setInterval(keepAliveServer, intervalRequest)
}
export function dataURItoBlob(dataURI: string) {
const mime = dataURI.split(",")[0].split(":")[1].split(";")[0]
const binary = atob(dataURI.split(",")[1])
const array = []
for (let i = 0; i < binary.length; i += 1) {
array.push(binary.charCodeAt(i))
}
return new Blob([new Uint8Array(array)], { type: mime })
}
export function loadImage(image: HTMLImageElement, src: string) {
return new Promise((resolve, reject) => {
const initSRC = image.src
const img = image
img.onload = resolve
img.onerror = (err) => {
img.src = initSRC
reject(err)
}
img.src = src
})
}
export function srcToFile(src: string, fileName: string, mimeType: string) {
return fetch(src)
.then(function (res) {
return res.arrayBuffer()
})
.then(function (buf) {
return new File([buf], fileName, { type: mimeType })
})
}
export async function askWritePermission() {
try {
// The clipboard-write permission is granted automatically to pages
// when they are the active tab. So it's not required, but it's more safe.
const { state } = await navigator.permissions.query({
name: "clipboard-write" as PermissionName,
})
return state === "granted"
} catch (error) {
// Browser compatibility / Security error (ONLY HTTPS) ...
return false
}
}
function canvasToBlob(canvas: HTMLCanvasElement, mime: string): Promise<any> {
return new Promise((resolve, reject) =>
canvas.toBlob(async (d) => {
if (d) {
resolve(d)
} else {
reject(new Error("Expected toBlob() to be defined"))
}
}, mime)
)
}
const setToClipboard = async (blob: any) => {
const data = [new ClipboardItem({ [blob.type]: blob })]
await navigator.clipboard.write(data)
}
export function isRightClick(ev: SyntheticEvent) {
const mouseEvent = ev.nativeEvent as MouseEvent
return mouseEvent.button === 2
}
export function isMidClick(ev: SyntheticEvent) {
const mouseEvent = ev.nativeEvent as MouseEvent
return mouseEvent.button === 1
}
export async function copyCanvasImage(canvas: HTMLCanvasElement) {
const blob = await canvasToBlob(canvas, "image/png")
try {
await setToClipboard(blob)
} catch {
console.log("Copy image failed!")
}
}
export function downloadImage(uri: string, name: string) {
const link = document.createElement("a")
link.href = uri
link.download = name
// this is necessary as link.click() does not work on the latest firefox
link.dispatchEvent(
new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
})
)
setTimeout(() => {
// For Firefox it is necessary to delay revoking the ObjectURL
// window.URL.revokeObjectURL(base64)
link.remove()
}, 100)
}