wip
This commit is contained in:
parent
7463a599a9
commit
43433c50eb
@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { nanoid } from "nanoid"
|
import { nanoid } from "nanoid"
|
||||||
import { useRecoilState, useSetRecoilState } from "recoil"
|
import { useSetRecoilState } from "recoil"
|
||||||
import { fileState, serverConfigState } from "@/lib/store"
|
import { serverConfigState } from "@/lib/store"
|
||||||
import useInputImage from "@/hooks/useInputImage"
|
import useInputImage from "@/hooks/useInputImage"
|
||||||
import { keepGUIAlive } from "@/lib/utils"
|
import { keepGUIAlive } from "@/lib/utils"
|
||||||
import { getServerConfig, isDesktop } from "@/lib/api"
|
import { getServerConfig, isDesktop } from "@/lib/api"
|
||||||
@ -9,6 +9,7 @@ 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"
|
||||||
|
|
||||||
const SUPPORTED_FILE_TYPE = [
|
const SUPPORTED_FILE_TYPE = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
@ -18,13 +19,15 @@ const SUPPORTED_FILE_TYPE = [
|
|||||||
"image/tiff",
|
"image/tiff",
|
||||||
]
|
]
|
||||||
function Home() {
|
function Home() {
|
||||||
const [file, setFile] = useRecoilState(fileState)
|
const [file, setFile] = useStore((state) => [state.file, state.setFile])
|
||||||
|
|
||||||
const userInputImage = useInputImage()
|
const userInputImage = useInputImage()
|
||||||
const setServerConfigState = useSetRecoilState(serverConfigState)
|
const setServerConfigState = useSetRecoilState(serverConfigState)
|
||||||
|
|
||||||
// Set Input Image
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (userInputImage) {
|
||||||
setFile(userInputImage)
|
setFile(userInputImage)
|
||||||
|
}
|
||||||
}, [userInputImage, setFile])
|
}, [userInputImage, setFile])
|
||||||
|
|
||||||
// Keeping GUI Window Open
|
// Keeping GUI Window Open
|
||||||
|
@ -1,15 +1,6 @@
|
|||||||
import React, {
|
import { SyntheticEvent, useCallback, useEffect, useRef, useState } from "react"
|
||||||
SyntheticEvent,
|
import { CursorArrowRaysIcon } from "@heroicons/react/24/outline"
|
||||||
useCallback,
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react"
|
|
||||||
import {
|
|
||||||
CursorArrowRaysIcon,
|
|
||||||
ArrowsPointingOutIcon,
|
|
||||||
ArrowDownTrayIcon,
|
|
||||||
} from "@heroicons/react/24/outline"
|
|
||||||
import {
|
import {
|
||||||
ReactZoomPanPinchRef,
|
ReactZoomPanPinchRef,
|
||||||
TransformComponent,
|
TransformComponent,
|
||||||
@ -19,7 +10,7 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"
|
|||||||
import { useWindowSize } from "react-use"
|
import { useWindowSize } from "react-use"
|
||||||
// import { useWindowSize, useKey, useKeyPressEvent } from "@uidotdev/usehooks"
|
// import { useWindowSize, useKey, useKeyPressEvent } from "@uidotdev/usehooks"
|
||||||
import inpaint, { downloadToOutput, runPlugin } from "@/lib/api"
|
import inpaint, { downloadToOutput, runPlugin } from "@/lib/api"
|
||||||
import { Button, IconButton } from "@/components/ui/button"
|
import { IconButton } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
askWritePermission,
|
askWritePermission,
|
||||||
copyCanvasImage,
|
copyCanvasImage,
|
||||||
@ -29,30 +20,22 @@ import {
|
|||||||
loadImage,
|
loadImage,
|
||||||
srcToFile,
|
srcToFile,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { Eraser, Eye, Redo, Undo } from "lucide-react"
|
import { Eraser, Eye, Redo, Undo, Expand, Download } from "lucide-react"
|
||||||
import {
|
import {
|
||||||
appState,
|
|
||||||
brushSizeState,
|
|
||||||
croperState,
|
croperState,
|
||||||
enableFileManagerState,
|
enableFileManagerState,
|
||||||
fileState,
|
|
||||||
imageHeightState,
|
|
||||||
imageWidthState,
|
|
||||||
interactiveSegClicksState,
|
interactiveSegClicksState,
|
||||||
isDiffusionModelsState,
|
isDiffusionModelsState,
|
||||||
isEnableAutoSavingState,
|
isEnableAutoSavingState,
|
||||||
isInpaintingState,
|
|
||||||
isInteractiveSegRunningState,
|
isInteractiveSegRunningState,
|
||||||
isInteractiveSegState,
|
isInteractiveSegState,
|
||||||
isPix2PixState,
|
isPix2PixState,
|
||||||
isPluginRunningState,
|
isPluginRunningState,
|
||||||
isProcessingState,
|
isProcessingState,
|
||||||
negativePropmtState,
|
negativePropmtState,
|
||||||
propmtState,
|
|
||||||
runManuallyState,
|
runManuallyState,
|
||||||
seedState,
|
seedState,
|
||||||
settingState,
|
settingState,
|
||||||
toastState,
|
|
||||||
} from "@/lib/store"
|
} from "@/lib/store"
|
||||||
// import Croper from "../Croper/Croper"
|
// import Croper from "../Croper/Croper"
|
||||||
import emitter, {
|
import emitter, {
|
||||||
@ -71,8 +54,9 @@ import { Slider } from "./ui/slider"
|
|||||||
// import InteractiveSegReplaceModal from "../InteractiveSeg/ReplaceModal"
|
// import InteractiveSegReplaceModal from "../InteractiveSeg/ReplaceModal"
|
||||||
import { PluginName } from "@/lib/types"
|
import { PluginName } from "@/lib/types"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
import { useHotkeys } from "react-hotkeys-hook"
|
||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
|
||||||
const TOOLBAR_SIZE = 200
|
const TOOLBAR_HEIGHT = 200
|
||||||
const MIN_BRUSH_SIZE = 10
|
const MIN_BRUSH_SIZE = 10
|
||||||
const MAX_BRUSH_SIZE = 200
|
const MAX_BRUSH_SIZE = 200
|
||||||
const BRUSH_COLOR = "#ffcc00bb"
|
const BRUSH_COLOR = "#ffcc00bb"
|
||||||
@ -110,15 +94,45 @@ function mouseXY(ev: SyntheticEvent) {
|
|||||||
return { x: mouseEvent.offsetX, y: mouseEvent.offsetY }
|
return { x: mouseEvent.offsetX, y: mouseEvent.offsetY }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Editor() {
|
interface EditorProps {
|
||||||
const [file, setFile] = useRecoilState(fileState)
|
file: File
|
||||||
const promptVal = useRecoilValue(propmtState)
|
}
|
||||||
|
|
||||||
|
export default function Editor(props: EditorProps) {
|
||||||
|
const { file } = props
|
||||||
|
const { toast } = useToast()
|
||||||
|
|
||||||
|
const [
|
||||||
|
isInpainting,
|
||||||
|
imageWidth,
|
||||||
|
imageHeight,
|
||||||
|
baseBrushSize,
|
||||||
|
brushScale,
|
||||||
|
promptVal,
|
||||||
|
setImageSize,
|
||||||
|
setBrushSize,
|
||||||
|
setIsInpainting,
|
||||||
|
] = useStore((state) => [
|
||||||
|
state.isInpainting,
|
||||||
|
state.imageWidth,
|
||||||
|
state.imageHeight,
|
||||||
|
state.brushSize,
|
||||||
|
state.brushSizeScale,
|
||||||
|
state.prompt,
|
||||||
|
state.setImageSize,
|
||||||
|
state.setBrushSize,
|
||||||
|
state.setIsInpainting,
|
||||||
|
])
|
||||||
|
const brushSize = baseBrushSize * brushScale
|
||||||
|
|
||||||
|
// 纯 local state
|
||||||
|
const [showOriginal, setShowOriginal] = useState(false)
|
||||||
|
|
||||||
|
//
|
||||||
const negativePromptVal = useRecoilValue(negativePropmtState)
|
const negativePromptVal = useRecoilValue(negativePropmtState)
|
||||||
const settings = useRecoilValue(settingState)
|
const settings = useRecoilValue(settingState)
|
||||||
const [seedVal, setSeed] = useRecoilState(seedState)
|
const [seedVal, setSeed] = useRecoilState(seedState)
|
||||||
const croperRect = useRecoilValue(croperState)
|
const croperRect = useRecoilValue(croperState)
|
||||||
const setToastState = useSetRecoilState(toastState)
|
|
||||||
const [isInpainting, setIsInpainting] = useRecoilState(isInpaintingState)
|
|
||||||
const setIsPluginRunning = useSetRecoilState(isPluginRunningState)
|
const setIsPluginRunning = useSetRecoilState(isPluginRunningState)
|
||||||
const isProcessing = useRecoilValue(isProcessingState)
|
const isProcessing = useRecoilValue(isProcessingState)
|
||||||
const runMannually = useRecoilValue(runManuallyState)
|
const runMannually = useRecoilValue(runManuallyState)
|
||||||
@ -152,8 +166,6 @@ export default function Editor() {
|
|||||||
|
|
||||||
const [clicks, setClicks] = useRecoilState(interactiveSegClicksState)
|
const [clicks, setClicks] = useRecoilState(interactiveSegClicksState)
|
||||||
|
|
||||||
const [brushSize, setBrushSize] = useRecoilState(brushSizeState)
|
|
||||||
|
|
||||||
const [original, isOriginalLoaded] = useImage(file)
|
const [original, isOriginalLoaded] = useImage(file)
|
||||||
const [renders, setRenders] = useState<HTMLImageElement[]>([])
|
const [renders, setRenders] = useState<HTMLImageElement[]>([])
|
||||||
const [context, setContext] = useState<CanvasRenderingContext2D>()
|
const [context, setContext] = useState<CanvasRenderingContext2D>()
|
||||||
@ -175,7 +187,6 @@ export default function Editor() {
|
|||||||
brushSize: 20,
|
brushSize: 20,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [showOriginal, setShowOriginal] = useState(false)
|
|
||||||
const [scale, setScale] = useState<number>(1)
|
const [scale, setScale] = useState<number>(1)
|
||||||
const [panned, setPanned] = useState<boolean>(false)
|
const [panned, setPanned] = useState<boolean>(false)
|
||||||
const [minScale, setMinScale] = useState<number>(1.0)
|
const [minScale, setMinScale] = useState<number>(1.0)
|
||||||
@ -198,10 +209,6 @@ export default function Editor() {
|
|||||||
const enableFileManager = useRecoilValue(enableFileManagerState)
|
const enableFileManager = useRecoilValue(enableFileManagerState)
|
||||||
const isEnableAutoSaving = useRecoilValue(isEnableAutoSavingState)
|
const isEnableAutoSaving = useRecoilValue(isEnableAutoSavingState)
|
||||||
|
|
||||||
const [imageWidth, setImageWidth] = useRecoilState(imageWidthState)
|
|
||||||
const [imageHeight, setImageHeight] = useRecoilState(imageHeightState)
|
|
||||||
const app = useRecoilValue(appState)
|
|
||||||
|
|
||||||
const draw = useCallback(
|
const draw = useCallback(
|
||||||
(render: HTMLImageElement, lineGroup: LineGroup) => {
|
(render: HTMLImageElement, lineGroup: LineGroup) => {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
@ -422,11 +429,10 @@ export default function Editor() {
|
|||||||
// clear redo stack
|
// clear redo stack
|
||||||
resetRedoState()
|
resetRedoState()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: e.message ? e.message : e.toString(),
|
title: "Uh oh! Something went wrong.",
|
||||||
state: "error",
|
description: e.message ? e.message : e.toString(),
|
||||||
duration: 4000,
|
|
||||||
})
|
})
|
||||||
drawOnCurrentRender([])
|
drawOnCurrentRender([])
|
||||||
}
|
}
|
||||||
@ -464,11 +470,9 @@ export default function Editor() {
|
|||||||
} else if (isPix2Pix) {
|
} else if (isPix2Pix) {
|
||||||
runInpainting(false, undefined, null)
|
runInpainting(false, undefined, null)
|
||||||
} else {
|
} else {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: "Please draw mask on picture",
|
description: "Please draw mask on picture.",
|
||||||
state: "error",
|
|
||||||
duration: 1500,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
emitter.emit(DREAM_BUTTON_MOUSE_LEAVE)
|
emitter.emit(DREAM_BUTTON_MOUSE_LEAVE)
|
||||||
@ -554,11 +558,9 @@ export default function Editor() {
|
|||||||
// 使用上一次 IS 的 mask 生成
|
// 使用上一次 IS 的 mask 生成
|
||||||
runInpainting(false, undefined, prevInteractiveSegMask, data.image)
|
runInpainting(false, undefined, prevInteractiveSegMask, data.image)
|
||||||
} else {
|
} else {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: "Please draw mask on picture",
|
description: "Please draw mask on picture.",
|
||||||
state: "error",
|
|
||||||
duration: 1500,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -577,11 +579,9 @@ export default function Editor() {
|
|||||||
// 使用上一次 IS 的 mask 生成
|
// 使用上一次 IS 的 mask 生成
|
||||||
runInpainting(false, undefined, prevInteractiveSegMask)
|
runInpainting(false, undefined, prevInteractiveSegMask)
|
||||||
} else {
|
} else {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: "No mask to reuse",
|
description: "No mask to reuse",
|
||||||
state: "error",
|
|
||||||
duration: 1500,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -628,8 +628,7 @@ export default function Editor() {
|
|||||||
const { blob } = res
|
const { blob } = res
|
||||||
const newRender = new Image()
|
const newRender = new Image()
|
||||||
await loadImage(newRender, blob)
|
await loadImage(newRender, blob)
|
||||||
setImageHeight(newRender.height)
|
setImageSize(newRender.height, newRender.width)
|
||||||
setImageWidth(newRender.width)
|
|
||||||
const newRenders = [...renders, newRender]
|
const newRenders = [...renders, newRender]
|
||||||
setRenders(newRenders)
|
setRenders(newRenders)
|
||||||
const newLineGroups = [...lineGroups, []]
|
const newLineGroups = [...lineGroups, []]
|
||||||
@ -638,15 +637,12 @@ export default function Editor() {
|
|||||||
const end = new Date()
|
const end = new Date()
|
||||||
const time = end.getTime() - start.getTime()
|
const time = end.getTime() - start.getTime()
|
||||||
|
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
description: `Run ${name} successfully in ${time / 1000}s`,
|
||||||
desc: `Run ${name} successfully in ${time / 1000}s`,
|
|
||||||
state: "success",
|
|
||||||
duration: 3000,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const rW = windowSize.width / newRender.width
|
const rW = windowSize.width / newRender.width
|
||||||
const rH = (windowSize.height - TOOLBAR_SIZE) / newRender.height
|
const rH = (windowSize.height - TOOLBAR_HEIGHT) / newRender.height
|
||||||
let s = 1.0
|
let s = 1.0
|
||||||
if (rW < 1 || rH < 1) {
|
if (rW < 1 || rH < 1) {
|
||||||
s = Math.min(rW, rH)
|
s = Math.min(rW, rH)
|
||||||
@ -655,11 +651,9 @@ export default function Editor() {
|
|||||||
setScale(s)
|
setScale(s)
|
||||||
viewportRef.current?.centerView(s, 1)
|
viewportRef.current?.centerView(s, 1)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: e.message ? e.message : e.toString(),
|
description: e.message ? e.message : e.toString(),
|
||||||
state: "error",
|
|
||||||
duration: 3000,
|
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setIsPluginRunning(false)
|
setIsPluginRunning(false)
|
||||||
@ -671,8 +665,7 @@ export default function Editor() {
|
|||||||
getCurrentRender,
|
getCurrentRender,
|
||||||
setIsPluginRunning,
|
setIsPluginRunning,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
setImageHeight,
|
setImageSize,
|
||||||
setImageWidth,
|
|
||||||
lineGroups,
|
lineGroups,
|
||||||
viewportRef,
|
viewportRef,
|
||||||
windowSize,
|
windowSize,
|
||||||
@ -753,11 +746,10 @@ export default function Editor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [width, height] = getCurrentWidthHeight()
|
const [width, height] = getCurrentWidthHeight()
|
||||||
setImageWidth(width)
|
setImageSize(width, height)
|
||||||
setImageHeight(height)
|
|
||||||
|
|
||||||
const rW = windowSize.width / width
|
const rW = windowSize.width / width
|
||||||
const rH = (windowSize.height - TOOLBAR_SIZE) / height
|
const rH = (windowSize.height - TOOLBAR_HEIGHT) / height
|
||||||
|
|
||||||
let s = 1.0
|
let s = 1.0
|
||||||
if (rW < 1 || rH < 1) {
|
if (rW < 1 || rH < 1) {
|
||||||
@ -950,11 +942,9 @@ export default function Editor() {
|
|||||||
}
|
}
|
||||||
img.src = blob
|
img.src = blob
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: e.message ? e.message : e.toString(),
|
description: e.message ? e.message : e.toString(),
|
||||||
state: "error",
|
|
||||||
duration: 4000,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setIsInteractiveSegRunning(false)
|
setIsInteractiveSegRunning(false)
|
||||||
@ -1280,18 +1270,14 @@ export default function Editor() {
|
|||||||
if ((enableFileManager || isEnableAutoSaving) && renders.length > 0) {
|
if ((enableFileManager || isEnableAutoSaving) && renders.length > 0) {
|
||||||
try {
|
try {
|
||||||
downloadToOutput(renders[renders.length - 1], file.name, file.type)
|
downloadToOutput(renders[renders.length - 1], file.name, file.type)
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
description: "Save image success",
|
||||||
desc: `Save image success`,
|
|
||||||
state: "success",
|
|
||||||
duration: 2000,
|
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setToastState({
|
toast({
|
||||||
open: true,
|
variant: "destructive",
|
||||||
desc: e.message ? e.message : e.toString(),
|
title: "Uh oh! Something went wrong.",
|
||||||
state: "error",
|
description: e.message ? e.message : e.toString(),
|
||||||
duration: 2000,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -1439,6 +1425,15 @@ export default function Editor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderBrush = (style: any) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute rounded-[50%] border-[1px] border-[solid] border-[#ffcc00] pointer-events-none bg-[#ffcc00bb]"
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSliderChange = (value: number) => {
|
const handleSliderChange = (value: number) => {
|
||||||
setBrushSize(value)
|
setBrushSize(value)
|
||||||
|
|
||||||
@ -1591,24 +1586,16 @@ export default function Editor() {
|
|||||||
{showBrush &&
|
{showBrush &&
|
||||||
!isInpainting &&
|
!isInpainting &&
|
||||||
!isPanning &&
|
!isPanning &&
|
||||||
(isInteractiveSeg ? (
|
(isInteractiveSeg
|
||||||
renderInteractiveSegCursor()
|
? renderInteractiveSegCursor()
|
||||||
) : (
|
: renderBrush(
|
||||||
<div
|
getBrushStyle(
|
||||||
className="absolute rounded-[50%] border-[1px] border-[solid] pointer-events-none"
|
|
||||||
style={getBrushStyle(
|
|
||||||
isChangingBrushSizeByMouse ? changeBrushSizeByMouseInit.x : x,
|
isChangingBrushSizeByMouse ? changeBrushSizeByMouseInit.x : x,
|
||||||
isChangingBrushSizeByMouse ? changeBrushSizeByMouseInit.y : y
|
isChangingBrushSizeByMouse ? changeBrushSizeByMouseInit.y : y
|
||||||
)}
|
)
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{showRefBrush && (
|
{showRefBrush && renderBrush(getBrushStyle(windowCenterX, windowCenterY))}
|
||||||
<div
|
|
||||||
className="absolute rounded-[50%] border-[1px] border-[solid] pointer-events-none"
|
|
||||||
style={getBrushStyle(windowCenterX, windowCenterY)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="fixed flex bottom-10 border px-4 py-2 rounded-[3rem] gap-8 items-center justify-center backdrop-filter backdrop-blur-md">
|
<div className="fixed flex bottom-10 border px-4 py-2 rounded-[3rem] gap-8 items-center justify-center backdrop-filter backdrop-blur-md">
|
||||||
<Slider
|
<Slider
|
||||||
@ -1617,7 +1604,7 @@ export default function Editor() {
|
|||||||
min={MIN_BRUSH_SIZE}
|
min={MIN_BRUSH_SIZE}
|
||||||
max={MAX_BRUSH_SIZE}
|
max={MAX_BRUSH_SIZE}
|
||||||
step={1}
|
step={1}
|
||||||
value={[brushSize]}
|
value={[baseBrushSize]}
|
||||||
onValueChange={(vals) => handleSliderChange(vals[0])}
|
onValueChange={(vals) => handleSliderChange(vals[0])}
|
||||||
onClick={() => setShowRefBrush(false)}
|
onClick={() => setShowRefBrush(false)}
|
||||||
/>
|
/>
|
||||||
@ -1627,7 +1614,7 @@ export default function Editor() {
|
|||||||
disabled={scale === minScale && panned === false}
|
disabled={scale === minScale && panned === false}
|
||||||
onClick={resetZoom}
|
onClick={resetZoom}
|
||||||
>
|
>
|
||||||
<ArrowsPointingOutIcon />
|
<Expand />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton tooltip="Undo" onClick={undo} disabled={disableUndo()}>
|
<IconButton tooltip="Undo" onClick={undo} disabled={disableUndo()}>
|
||||||
<Undo />
|
<Undo />
|
||||||
@ -1662,10 +1649,9 @@ export default function Editor() {
|
|||||||
disabled={!renders.length}
|
disabled={!renders.length}
|
||||||
onClick={download}
|
onClick={download}
|
||||||
>
|
>
|
||||||
<ArrowDownTrayIcon />
|
<Download />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
{settings.runInpaintingManually && !isDiffusionModels && (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip="Run Inpainting"
|
tooltip="Run Inpainting"
|
||||||
disabled={
|
disabled={
|
||||||
@ -1679,7 +1665,6 @@ export default function Editor() {
|
|||||||
>
|
>
|
||||||
<Eraser />
|
<Eraser />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <InteractiveSegReplaceModal
|
{/* <InteractiveSegReplaceModal
|
||||||
|
@ -7,28 +7,16 @@ import {
|
|||||||
FormEvent,
|
FormEvent,
|
||||||
} from "react"
|
} from "react"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { useRecoilState } from "recoil"
|
|
||||||
import PhotoAlbum from "react-photo-album"
|
import PhotoAlbum from "react-photo-album"
|
||||||
import {
|
import { BarsArrowDownIcon, BarsArrowUpIcon } from "@heroicons/react/24/outline"
|
||||||
BarsArrowDownIcon,
|
|
||||||
BarsArrowUpIcon,
|
|
||||||
FolderIcon,
|
|
||||||
} from "@heroicons/react/24/outline"
|
|
||||||
import {
|
import {
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
ViewHorizontalIcon,
|
ViewHorizontalIcon,
|
||||||
ViewGridIcon,
|
ViewGridIcon,
|
||||||
} from "@radix-ui/react-icons"
|
} from "@radix-ui/react-icons"
|
||||||
import { useDebounce, useToggle } from "react-use"
|
import { useToggle } from "react-use"
|
||||||
|
import { useDebounce } from "@uidotdev/usehooks"
|
||||||
import FlexSearch from "flexsearch/dist/flexsearch.bundle.js"
|
import FlexSearch from "flexsearch/dist/flexsearch.bundle.js"
|
||||||
import {
|
|
||||||
fileManagerLayout,
|
|
||||||
fileManagerSearchText,
|
|
||||||
fileManagerSortBy,
|
|
||||||
fileManagerSortOrder,
|
|
||||||
SortBy,
|
|
||||||
SortOrder,
|
|
||||||
} from "@/lib/store"
|
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { API_ENDPOINT, getMedias } from "@/lib/api"
|
import { API_ENDPOINT, getMedias } from "@/lib/api"
|
||||||
import { IconButton } from "./ui/button"
|
import { IconButton } from "./ui/button"
|
||||||
@ -45,6 +33,9 @@ import {
|
|||||||
import { ScrollArea } from "./ui/scroll-area"
|
import { ScrollArea } from "./ui/scroll-area"
|
||||||
import { DialogTrigger } from "@radix-ui/react-dialog"
|
import { DialogTrigger } from "@radix-ui/react-dialog"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
import { useHotkeys } from "react-hotkeys-hook"
|
||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
import { SortBy, SortOrder } from "@/lib/types"
|
||||||
|
import { FolderClosed } from "lucide-react"
|
||||||
|
|
||||||
interface Photo {
|
interface Photo {
|
||||||
src: string
|
src: string
|
||||||
@ -83,6 +74,20 @@ export default function FileManager(props: Props) {
|
|||||||
const { onPhotoClick, photoWidth } = props
|
const { onPhotoClick, photoWidth } = props
|
||||||
const [open, toggleOpen] = useToggle(false)
|
const [open, toggleOpen] = useToggle(false)
|
||||||
|
|
||||||
|
const [
|
||||||
|
fileManagerState,
|
||||||
|
setFileManagerLayout,
|
||||||
|
setFileManagerSortBy,
|
||||||
|
setFileManagerSortOrder,
|
||||||
|
setFileManagerSearchText,
|
||||||
|
] = useStore((state) => [
|
||||||
|
state.fileManagerState,
|
||||||
|
state.setFileManagerLayout,
|
||||||
|
state.setFileManagerSortBy,
|
||||||
|
state.setFileManagerSortOrder,
|
||||||
|
state.setFileManagerSearchText,
|
||||||
|
])
|
||||||
|
|
||||||
useHotkeys("f", () => {
|
useHotkeys("f", () => {
|
||||||
toggleOpen()
|
toggleOpen()
|
||||||
})
|
})
|
||||||
@ -91,25 +96,11 @@ export default function FileManager(props: Props) {
|
|||||||
const [scrollTop, setScrollTop] = useState(0)
|
const [scrollTop, setScrollTop] = useState(0)
|
||||||
const [closeScrollTop, setCloseScrollTop] = useState(0)
|
const [closeScrollTop, setCloseScrollTop] = useState(0)
|
||||||
|
|
||||||
const [sortBy, setSortBy] = useRecoilState<SortBy>(fileManagerSortBy)
|
|
||||||
const [sortOrder, setSortOrder] = useRecoilState(fileManagerSortOrder)
|
|
||||||
const [layout, setLayout] = useRecoilState(fileManagerLayout)
|
|
||||||
const [debouncedSearchText, setDebouncedSearchText] = useRecoilState(
|
|
||||||
fileManagerSearchText
|
|
||||||
)
|
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
const [searchText, setSearchText] = useState(debouncedSearchText)
|
const debouncedSearchText = useDebounce(fileManagerState.searchText, 300)
|
||||||
const [tab, setTab] = useState(IMAGE_TAB)
|
const [tab, setTab] = useState(IMAGE_TAB)
|
||||||
const [photos, setPhotos] = useState<Photo[]>([])
|
const [photos, setPhotos] = useState<Photo[]>([])
|
||||||
|
|
||||||
const [, cancel] = useDebounce(
|
|
||||||
() => {
|
|
||||||
setDebouncedSearchText(searchText)
|
|
||||||
},
|
|
||||||
300,
|
|
||||||
[searchText]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setCloseScrollTop(scrollTop)
|
setCloseScrollTop(scrollTop)
|
||||||
@ -153,7 +144,11 @@ export default function FileManager(props: Props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredFilenames = _.orderBy(filteredFilenames, sortBy, sortOrder)
|
filteredFilenames = _.orderBy(
|
||||||
|
filteredFilenames,
|
||||||
|
fileManagerState.sortBy,
|
||||||
|
fileManagerState.sortOrder
|
||||||
|
)
|
||||||
|
|
||||||
const newPhotos = filteredFilenames.map((filename: Filename) => {
|
const newPhotos = filteredFilenames.map((filename: Filename) => {
|
||||||
const width = photoWidth
|
const width = photoWidth
|
||||||
@ -171,7 +166,7 @@ export default function FileManager(props: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchData()
|
fetchData()
|
||||||
}, [tab, debouncedSearchText, sortBy, sortOrder, photoWidth, open])
|
}, [tab, debouncedSearchText, fileManagerState, photoWidth, open])
|
||||||
|
|
||||||
const onScroll = (event: SyntheticEvent) => {
|
const onScroll = (event: SyntheticEvent) => {
|
||||||
setScrollTop(event.currentTarget.scrollTop)
|
setScrollTop(event.currentTarget.scrollTop)
|
||||||
@ -190,19 +185,21 @@ export default function FileManager(props: Props) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
tooltip="Rows layout"
|
tooltip="Rows layout"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLayout("rows")
|
setFileManagerLayout("rows")
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ViewHorizontalIcon
|
<ViewHorizontalIcon
|
||||||
className={layout !== "rows" ? "opacity-50" : ""}
|
className={fileManagerState.layout !== "rows" ? "opacity-50" : ""}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip="Grid layout"
|
tooltip="Grid layout"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLayout("masonry")
|
setFileManagerLayout("masonry")
|
||||||
}}
|
}}
|
||||||
className={layout !== "masonry" ? "opacity-50" : ""}
|
className={
|
||||||
|
fileManagerState.layout !== "masonry" ? "opacity-50" : ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ViewGridIcon />
|
<ViewGridIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -213,9 +210,9 @@ export default function FileManager(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={toggleOpen}>
|
<Dialog open={open} onOpenChange={toggleOpen}>
|
||||||
<DialogTrigger>
|
<DialogTrigger asChild>
|
||||||
<IconButton tooltip="File Manager">
|
<IconButton tooltip="File Manager">
|
||||||
<FolderIcon />
|
<FolderClosed />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="h-4/5 max-w-6xl">
|
<DialogContent className="h-4/5 max-w-6xl">
|
||||||
@ -225,14 +222,14 @@ export default function FileManager(props: Props) {
|
|||||||
<MagnifyingGlassIcon className="absolute left-[8px]" />
|
<MagnifyingGlassIcon className="absolute left-[8px]" />
|
||||||
<Input
|
<Input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
value={searchText}
|
value={fileManagerState.searchText}
|
||||||
className="w-[250px] pl-[30px]"
|
className="w-[250px] pl-[30px]"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onInput={(evt: FormEvent<HTMLInputElement>) => {
|
onInput={(evt: FormEvent<HTMLInputElement>) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
const target = evt.target as HTMLInputElement
|
const target = evt.target as HTMLInputElement
|
||||||
setSearchText(target.value)
|
setFileManagerSearchText(target.value)
|
||||||
}}
|
}}
|
||||||
placeholder="Search by file name"
|
placeholder="Search by file name"
|
||||||
/>
|
/>
|
||||||
@ -248,17 +245,17 @@ export default function FileManager(props: Props) {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<Select
|
<Select
|
||||||
value={SortByMap[sortBy]}
|
value={SortByMap[fileManagerState.sortBy]}
|
||||||
onValueChange={(val) => {
|
onValueChange={(val) => {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case SORT_BY_NAME:
|
case SORT_BY_NAME:
|
||||||
setSortBy(SortBy.NAME)
|
setFileManagerSortBy(SortBy.NAME)
|
||||||
break
|
break
|
||||||
case SORT_BY_CREATED_TIME:
|
case SORT_BY_CREATED_TIME:
|
||||||
setSortBy(SortBy.CTIME)
|
setFileManagerSortBy(SortBy.CTIME)
|
||||||
break
|
break
|
||||||
case SORT_BY_MODIFIED_TIME:
|
case SORT_BY_MODIFIED_TIME:
|
||||||
setSortBy(SortBy.MTIME)
|
setFileManagerSortBy(SortBy.MTIME)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -279,11 +276,11 @@ export default function FileManager(props: Props) {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{sortOrder === SortOrder.DESCENDING ? (
|
{fileManagerState.sortOrder === SortOrder.DESCENDING ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip="Descending Order"
|
tooltip="Descending Order"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSortOrder(SortOrder.ASCENDING)
|
setFileManagerSortOrder(SortOrder.ASCENDING)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BarsArrowDownIcon />
|
<BarsArrowDownIcon />
|
||||||
@ -292,7 +289,7 @@ export default function FileManager(props: Props) {
|
|||||||
<IconButton
|
<IconButton
|
||||||
tooltip="Ascending Order"
|
tooltip="Ascending Order"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSortOrder(SortOrder.DESCENDING)
|
setFileManagerSortOrder(SortOrder.DESCENDING)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BarsArrowUpIcon />
|
<BarsArrowUpIcon />
|
||||||
@ -308,7 +305,7 @@ export default function FileManager(props: Props) {
|
|||||||
ref={onRefChange}
|
ref={onRefChange}
|
||||||
>
|
>
|
||||||
<PhotoAlbum
|
<PhotoAlbum
|
||||||
layout={layout}
|
layout={fileManagerState.layout}
|
||||||
photos={photos}
|
photos={photos}
|
||||||
spacing={12}
|
spacing={12}
|
||||||
padding={0}
|
padding={0}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react"
|
import { useState } from "react"
|
||||||
import useResolution from "@/hooks/useResolution"
|
import useResolution from "@/hooks/useResolution"
|
||||||
|
|
||||||
type FileSelectProps = {
|
type FileSelectProps = {
|
||||||
@ -34,10 +34,10 @@ export default function FileSelect(props: FileSelectProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute flex w-screen h-screen justify-center items-center ">
|
<div className="absolute flex w-screen h-screen justify-center items-center pointer-events-none">
|
||||||
<label
|
<label
|
||||||
htmlFor={uploadElemId}
|
htmlFor={uploadElemId}
|
||||||
className="grid cursor-pointer border-[2px] border-[dashed] rounded-lg min-w-[600px] hover:cursor-pointer hover:bg-primary hover:text-primary-foreground"
|
className="grid 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"
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import { FolderIcon, PhotoIcon } from "@heroicons/react/24/outline"
|
|
||||||
import { PlayIcon } from "@radix-ui/react-icons"
|
import { PlayIcon } from "@radix-ui/react-icons"
|
||||||
import React, { useCallback, useState } from "react"
|
import React, { useCallback, useState } from "react"
|
||||||
import { useRecoilState, useRecoilValue } from "recoil"
|
import { useRecoilState, useRecoilValue } from "recoil"
|
||||||
import { useHotkeys } from "react-hotkeys-hook"
|
import { useHotkeys } from "react-hotkeys-hook"
|
||||||
import {
|
import {
|
||||||
enableFileManagerState,
|
enableFileManagerState,
|
||||||
fileState,
|
|
||||||
isInpaintingState,
|
|
||||||
isPix2PixState,
|
isPix2PixState,
|
||||||
isSDState,
|
isSDState,
|
||||||
maskState,
|
maskState,
|
||||||
runManuallyState,
|
runManuallyState,
|
||||||
showFileManagerState,
|
|
||||||
} from "@/lib/store"
|
} from "@/lib/store"
|
||||||
import { Button, IconButton, ImageUploadButton } from "@/components/ui/button"
|
import { IconButton, ImageUploadButton } from "@/components/ui/button"
|
||||||
import Shortcuts from "@/components/Shortcuts"
|
import Shortcuts from "@/components/Shortcuts"
|
||||||
// import SettingIcon from "../Settings/SettingIcon"
|
// import SettingIcon from "../Settings/SettingIcon"
|
||||||
// import PromptInput from "./PromptInput"
|
// import PromptInput from "./PromptInput"
|
||||||
@ -28,15 +24,19 @@ import { useImage } from "@/hooks/useImage"
|
|||||||
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
||||||
import PromptInput from "./PromptInput"
|
import PromptInput from "./PromptInput"
|
||||||
import { RotateCw } from "lucide-react"
|
import { RotateCw, Image } from "lucide-react"
|
||||||
import FileManager from "./FileManager"
|
import FileManager from "./FileManager"
|
||||||
import { getMediaFile } from "@/lib/api"
|
import { getMediaFile } from "@/lib/api"
|
||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const isInpainting = useRecoilValue(isInpaintingState)
|
const [file, isInpainting, setFile] = useStore((state) => [
|
||||||
const [file, setFile] = useRecoilState(fileState)
|
state.file,
|
||||||
|
state.isInpainting,
|
||||||
|
state.setFile,
|
||||||
|
])
|
||||||
const [mask, setMask] = useRecoilState(maskState)
|
const [mask, setMask] = useRecoilState(maskState)
|
||||||
const [maskImage, maskImageLoaded] = useImage(mask)
|
// const [maskImage, maskImageLoaded] = useImage(mask)
|
||||||
const isSD = useRecoilValue(isSDState)
|
const isSD = useRecoilValue(isSDState)
|
||||||
const isPix2Pix = useRecoilValue(isPix2PixState)
|
const isPix2Pix = useRecoilValue(isPix2PixState)
|
||||||
const runManually = useRecoilValue(runManuallyState)
|
const runManually = useRecoilValue(runManuallyState)
|
||||||
@ -88,15 +88,13 @@ const Header = () => {
|
|||||||
setFile(file)
|
setFile(file)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PhotoIcon />
|
<Image />
|
||||||
</ImageUploadButton>
|
</ImageUploadButton>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
className="flex items-center"
|
||||||
style={{
|
style={{
|
||||||
visibility: file ? "visible" : "hidden",
|
visibility: file ? "visible" : "hidden",
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ImageUploadButton
|
<ImageUploadButton
|
||||||
@ -133,13 +131,13 @@ const Header = () => {
|
|||||||
<PlayIcon />
|
<PlayIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent>
|
{/* <PopoverContent>
|
||||||
{maskImageLoaded ? (
|
{maskImageLoaded ? (
|
||||||
<img src={maskImage.src} alt="Custom mask" />
|
<img src={maskImage.src} alt="Custom mask" />
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</PopoverContent>
|
</PopoverContent> */}
|
||||||
</Popover>
|
</Popover>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -159,13 +157,11 @@ const Header = () => {
|
|||||||
|
|
||||||
{isSD ? <PromptInput /> : <></>}
|
{isSD ? <PromptInput /> : <></>}
|
||||||
|
|
||||||
<div className="header-icons-wrapper">
|
|
||||||
{/* <CoffeeIcon /> */}
|
{/* <CoffeeIcon /> */}
|
||||||
<div className="header-icons">
|
<div>
|
||||||
<Shortcuts />
|
<Shortcuts />
|
||||||
{/* <SettingIcon /> */}
|
{/* <SettingIcon /> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
20
web_app/src/components/ImageSize.tsx
Normal file
20
web_app/src/components/ImageSize.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
|
||||||
|
const ImageSize = () => {
|
||||||
|
const [imageWidth, imageHeight] = useStore((state) => [
|
||||||
|
state.imageWidth,
|
||||||
|
state.imageHeight,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!imageWidth || !imageHeight) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border rounded-lg px-2 py-[6px] z-10">
|
||||||
|
{imageWidth}x{imageHeight}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageSize
|
131
web_app/src/components/Plugins.tsx
Normal file
131
web_app/src/components/Plugins.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "./ui/dropdown-menu"
|
||||||
|
import { Button } from "./ui/button"
|
||||||
|
import { Fullscreen, MousePointerClick, Slice, Smile } from "lucide-react"
|
||||||
|
import { MixIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
export enum PluginName {
|
||||||
|
RemoveBG = "RemoveBG",
|
||||||
|
AnimeSeg = "AnimeSeg",
|
||||||
|
RealESRGAN = "RealESRGAN",
|
||||||
|
GFPGAN = "GFPGAN",
|
||||||
|
RestoreFormer = "RestoreFormer",
|
||||||
|
InteractiveSeg = "InteractiveSeg",
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginMap = {
|
||||||
|
[PluginName.RemoveBG]: {
|
||||||
|
IconClass: Slice,
|
||||||
|
showName: "RemoveBG",
|
||||||
|
},
|
||||||
|
[PluginName.AnimeSeg]: {
|
||||||
|
IconClass: Slice,
|
||||||
|
showName: "Anime Segmentation",
|
||||||
|
},
|
||||||
|
[PluginName.RealESRGAN]: {
|
||||||
|
IconClass: Fullscreen,
|
||||||
|
showName: "RealESRGAN 4x",
|
||||||
|
},
|
||||||
|
[PluginName.GFPGAN]: {
|
||||||
|
IconClass: Smile,
|
||||||
|
showName: "GFPGAN",
|
||||||
|
},
|
||||||
|
[PluginName.RestoreFormer]: {
|
||||||
|
IconClass: Smile,
|
||||||
|
showName: "RestoreFormer",
|
||||||
|
},
|
||||||
|
[PluginName.InteractiveSeg]: {
|
||||||
|
IconClass: MousePointerClick,
|
||||||
|
showName: "Interactive Segmentation",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Plugins = () => {
|
||||||
|
// const [open, toggleOpen] = useToggle(true)
|
||||||
|
// const serverConfig = useRecoilValue(serverConfigState)
|
||||||
|
// const isProcessing = useRecoilValue(isProcessingState)
|
||||||
|
const plugins = [
|
||||||
|
PluginName.RemoveBG,
|
||||||
|
PluginName.AnimeSeg,
|
||||||
|
PluginName.RealESRGAN,
|
||||||
|
PluginName.GFPGAN,
|
||||||
|
PluginName.RestoreFormer,
|
||||||
|
PluginName.InteractiveSeg,
|
||||||
|
]
|
||||||
|
|
||||||
|
if (plugins.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPluginClick = (pluginName: string) => {
|
||||||
|
// if (!disabled) {
|
||||||
|
// emitter.emit(pluginName)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRealESRGANClick = (upscale: number) => {
|
||||||
|
// if (!disabled) {
|
||||||
|
// emitter.emit(PluginName.RealESRGAN, { upscale })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderRealESRGANPlugin = () => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuSub key="RealESRGAN">
|
||||||
|
<DropdownMenuSubTrigger>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<Fullscreen />
|
||||||
|
RealESRGAN
|
||||||
|
</div>
|
||||||
|
</DropdownMenuSubTrigger>
|
||||||
|
<DropdownMenuSubContent>
|
||||||
|
<DropdownMenuItem onClick={() => onRealESRGANClick(2)}>
|
||||||
|
upscale 2x
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => onRealESRGANClick(4)}>
|
||||||
|
upscale 4x
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</DropdownMenuSub>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderPlugins = () => {
|
||||||
|
return plugins.map((plugin: PluginName) => {
|
||||||
|
const { IconClass, showName } = pluginMap[plugin]
|
||||||
|
if (plugin === PluginName.RealESRGAN) {
|
||||||
|
return renderRealESRGANPlugin()
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem key={plugin} onClick={() => onPluginClick(plugin)}>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<IconClass className="p-1" />
|
||||||
|
{showName}
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu modal={false}>
|
||||||
|
<DropdownMenuTrigger className="border rounded-lg z-10">
|
||||||
|
<Button variant="ghost" size="icon" asChild>
|
||||||
|
<MixIcon className="p-2" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent side="bottom" align="start">
|
||||||
|
{renderPlugins()}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Plugins
|
@ -1,18 +1,19 @@
|
|||||||
import React, { FormEvent } from "react"
|
import React, { FormEvent } from "react"
|
||||||
import { useRecoilState, useRecoilValue } from "recoil"
|
|
||||||
import emitter, {
|
import emitter, {
|
||||||
DREAM_BUTTON_MOUSE_ENTER,
|
DREAM_BUTTON_MOUSE_ENTER,
|
||||||
DREAM_BUTTON_MOUSE_LEAVE,
|
DREAM_BUTTON_MOUSE_LEAVE,
|
||||||
EVENT_PROMPT,
|
EVENT_PROMPT,
|
||||||
} from "@/lib/event"
|
} from "@/lib/event"
|
||||||
import { appState, isInpaintingState, propmtState } from "@/lib/store"
|
|
||||||
import { Button } from "./ui/button"
|
import { Button } from "./ui/button"
|
||||||
import { Input } from "./ui/input"
|
import { Input } from "./ui/input"
|
||||||
|
import { useStore } from "@/lib/states"
|
||||||
|
|
||||||
const PromptInput = () => {
|
const PromptInput = () => {
|
||||||
const app = useRecoilValue(appState)
|
const [isInpainting, prompt, setPrompt] = useStore((state) => [
|
||||||
const [prompt, setPrompt] = useRecoilState(propmtState)
|
state.isInpainting,
|
||||||
const isInpainting = useRecoilValue(isInpaintingState)
|
state.prompt,
|
||||||
|
state.setPrompt,
|
||||||
|
])
|
||||||
|
|
||||||
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
|
const handleOnInput = (evt: FormEvent<HTMLInputElement>) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
@ -22,7 +23,7 @@ const PromptInput = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleRepaintClick = () => {
|
const handleRepaintClick = () => {
|
||||||
if (prompt.length !== 0 && !app.isInpainting) {
|
if (prompt.length !== 0 && isInpainting) {
|
||||||
emitter.emit(EVENT_PROMPT)
|
emitter.emit(EVENT_PROMPT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +54,7 @@ const PromptInput = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleRepaintClick}
|
onClick={handleRepaintClick}
|
||||||
disabled={prompt.length === 0 || app.isInpainting}
|
disabled={prompt.length === 0 || isInpainting}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Keyboard } from "lucide-react"
|
import { Keyboard } from "lucide-react"
|
||||||
import useHotKey from "@/hooks/useHotkey"
|
|
||||||
import { IconButton } from "@/components/ui/button"
|
import { IconButton } from "@/components/ui/button"
|
||||||
import { useToggle } from "@uidotdev/usehooks"
|
import { useToggle } from "@uidotdev/usehooks"
|
||||||
import {
|
import {
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "./ui/dialog"
|
} from "./ui/dialog"
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook"
|
||||||
|
|
||||||
interface ShortcutProps {
|
interface ShortcutProps {
|
||||||
content: string
|
content: string
|
||||||
@ -21,7 +21,7 @@ function ShortCut(props: ShortcutProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="shortcut-description">{content}</div>
|
<div>{content}</div>
|
||||||
<div className="flex gap-[8px]">
|
<div className="flex gap-[8px]">
|
||||||
{keys.map((k) => (
|
{keys.map((k) => (
|
||||||
// TODO: 优化快捷键显示
|
// TODO: 优化快捷键显示
|
||||||
@ -49,13 +49,13 @@ const CmdOrCtrl = () => {
|
|||||||
export function Shortcuts() {
|
export function Shortcuts() {
|
||||||
const [open, toggleOpen] = useToggle(false)
|
const [open, toggleOpen] = useToggle(false)
|
||||||
|
|
||||||
useHotKey("h", () => {
|
useHotkeys("h", () => {
|
||||||
toggleOpen()
|
toggleOpen()
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={toggleOpen}>
|
<Dialog open={open} onOpenChange={toggleOpen}>
|
||||||
<DialogTrigger>
|
<DialogTrigger asChild>
|
||||||
<IconButton tooltip="Hotkeys">
|
<IconButton tooltip="Hotkeys">
|
||||||
<Keyboard />
|
<Keyboard />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -63,7 +63,7 @@ export function Shortcuts() {
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Hotkeys</DialogTitle>
|
<DialogTitle>Hotkeys</DialogTitle>
|
||||||
<DialogDescription className="flex gap-2 flex-col pt-4">
|
<div className="flex gap-2 flex-col pt-4">
|
||||||
<ShortCut content="Pan" keys={["Space + Drag"]} />
|
<ShortCut content="Pan" keys={["Space + Drag"]} />
|
||||||
<ShortCut content="Reset Zoom/Pan" keys={["Esc"]} />
|
<ShortCut content="Reset Zoom/Pan" keys={["Esc"]} />
|
||||||
<ShortCut content="Decrease Brush Size" keys={["["]} />
|
<ShortCut content="Decrease Brush Size" keys={["["]} />
|
||||||
@ -87,7 +87,7 @@ export function Shortcuts() {
|
|||||||
<ShortCut content="Toggle Hotkeys Dialog" keys={["H"]} />
|
<ShortCut content="Toggle Hotkeys Dialog" keys={["H"]} />
|
||||||
<ShortCut content="Toggle Settings Dialog" keys={["S"]} />
|
<ShortCut content="Toggle Settings Dialog" keys={["S"]} />
|
||||||
<ShortCut content="Toggle File Manager" keys={["F"]} />
|
<ShortCut content="Toggle File Manager" keys={["F"]} />
|
||||||
</DialogDescription>
|
</div>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -1,36 +1,28 @@
|
|||||||
import React, { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"
|
||||||
import Editor from "./Editor"
|
import Editor from "./Editor"
|
||||||
// import SettingModal from "./Settings/SettingsModal"
|
// import SettingModal from "./Settings/SettingsModal"
|
||||||
// import Toast from "./shared/Toast"
|
|
||||||
import {
|
import {
|
||||||
AIModel,
|
AIModel,
|
||||||
fileState,
|
|
||||||
isPaintByExampleState,
|
isPaintByExampleState,
|
||||||
isPix2PixState,
|
isPix2PixState,
|
||||||
isSDState,
|
isSDState,
|
||||||
settingState,
|
settingState,
|
||||||
showFileManagerState,
|
|
||||||
toastState,
|
|
||||||
} from "@/lib/store"
|
} from "@/lib/store"
|
||||||
import {
|
import { currentModel, modelDownloaded, switchModel } from "@/lib/api"
|
||||||
currentModel,
|
import { useStore } from "@/lib/states"
|
||||||
getMediaFile,
|
import ImageSize from "./ImageSize"
|
||||||
modelDownloaded,
|
import Plugins from "./Plugins"
|
||||||
switchModel,
|
|
||||||
} from "@/lib/api"
|
|
||||||
// import SidePanel from "./SidePanel/SidePanel"
|
// import SidePanel from "./SidePanel/SidePanel"
|
||||||
// import PESidePanel from "./SidePanel/PESidePanel"
|
// import PESidePanel from "./SidePanel/PESidePanel"
|
||||||
// import FileManager from "./FileManager/FileManager"
|
|
||||||
// import P2PSidePanel from "./SidePanel/P2PSidePanel"
|
// import P2PSidePanel from "./SidePanel/P2PSidePanel"
|
||||||
// import Plugins from "./Plugins/Plugins"
|
// import Plugins from "./Plugins/Plugins"
|
||||||
// import Flex from "./shared/Layout"
|
// import Flex from "./shared/Layout"
|
||||||
// import ImageSize from "./ImageSize/ImageSize"
|
// import ImageSize from "./ImageSize/ImageSize"
|
||||||
|
|
||||||
const Workspace = () => {
|
const Workspace = () => {
|
||||||
const setFile = useSetRecoilState(fileState)
|
const file = useStore((state) => state.file)
|
||||||
const [settings, setSettingState] = useRecoilState(settingState)
|
const [settings, setSettingState] = useRecoilState(settingState)
|
||||||
const [toastVal, setToastState] = useRecoilState(toastState)
|
|
||||||
const isSD = useRecoilValue(isSDState)
|
const isSD = useRecoilValue(isSDState)
|
||||||
const isPaintByExample = useRecoilValue(isPaintByExampleState)
|
const isPaintByExample = useRecoilValue(isPaintByExampleState)
|
||||||
const isPix2Pix = useRecoilValue(isPix2PixState)
|
const isPix2Pix = useRecoilValue(isPix2PixState)
|
||||||
@ -53,33 +45,34 @@ const Workspace = () => {
|
|||||||
loadingDuration = 9999999999
|
loadingDuration = 9999999999
|
||||||
}
|
}
|
||||||
|
|
||||||
setToastState({
|
// TODO 修改成 Modal
|
||||||
open: true,
|
// setToastState({
|
||||||
desc: loadingMessage,
|
// open: true,
|
||||||
state: "loading",
|
// desc: loadingMessage,
|
||||||
duration: loadingDuration,
|
// state: "loading",
|
||||||
})
|
// duration: loadingDuration,
|
||||||
|
// })
|
||||||
|
|
||||||
switchModel(model)
|
switchModel(model)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setToastState({
|
// setToastState({
|
||||||
open: true,
|
// open: true,
|
||||||
desc: `Switch to ${model} model success`,
|
// desc: `Switch to ${model} model success`,
|
||||||
state: "success",
|
// state: "success",
|
||||||
duration: 3000,
|
// duration: 3000,
|
||||||
})
|
// })
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Server error")
|
throw new Error("Server error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastState({
|
// setToastState({
|
||||||
open: true,
|
// open: true,
|
||||||
desc: `Switch to ${model} model failed`,
|
// desc: `Switch to ${model} model failed`,
|
||||||
state: "error",
|
// state: "error",
|
||||||
duration: 3000,
|
// duration: 3000,
|
||||||
})
|
// })
|
||||||
setSettingState((old) => {
|
setSettingState((old) => {
|
||||||
return { ...old, model: curModel as AIModel }
|
return { ...old, model: curModel as AIModel }
|
||||||
})
|
})
|
||||||
@ -101,12 +94,12 @@ const Workspace = () => {
|
|||||||
{/* {isSD ? <SidePanel /> : <></>}
|
{/* {isSD ? <SidePanel /> : <></>}
|
||||||
{isPaintByExample ? <PESidePanel /> : <></>}
|
{isPaintByExample ? <PESidePanel /> : <></>}
|
||||||
{isPix2Pix ? <P2PSidePanel /> : <></>}
|
{isPix2Pix ? <P2PSidePanel /> : <></>}
|
||||||
<Flex style={{ position: "absolute", top: 68, left: 24, gap: 12 }}>
|
{/* <SettingModal onClose={onSettingClose} /> */}
|
||||||
|
<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
|
||||||
<Plugins />
|
<Plugins />
|
||||||
<ImageSize />
|
<ImageSize />
|
||||||
</Flex>
|
</div>
|
||||||
{/* <SettingModal onClose={onSettingClose} /> */}
|
{file ? <Editor file={file} /> : <></>}
|
||||||
<Editor />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -65,15 +65,22 @@ export interface IconButtonProps extends ButtonProps {
|
|||||||
tooltip: string
|
tooltip: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const IconButton = (props: IconButtonProps) => {
|
const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||||
const { tooltip, children, ...rest } = props
|
({ tooltip, children, ...rest }, ref) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" {...rest} asChild>
|
<Button
|
||||||
<div className="p-[8px]">{children}</div>
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
{...rest}
|
||||||
|
ref={ref}
|
||||||
|
tabIndex={-1}
|
||||||
|
className="cursor-default"
|
||||||
|
>
|
||||||
|
<div className="icon-button-icon-wrapper">{children}</div>
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@ -83,7 +90,8 @@ const IconButton = (props: IconButtonProps) => {
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export interface UploadButtonProps extends IconButtonProps {
|
export interface UploadButtonProps extends IconButtonProps {
|
||||||
onFileUpload: (file: File) => void
|
onFileUpload: (file: File) => void
|
||||||
@ -106,7 +114,9 @@ const ImageUploadButton = (props: UploadButtonProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label htmlFor={uploadElemId}>
|
<label htmlFor={uploadElemId}>
|
||||||
<IconButton {...rest}>{children}</IconButton>
|
<IconButton {...rest} asChild>
|
||||||
|
{children}
|
||||||
|
</IconButton>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
style={{ display: "none" }}
|
style={{ display: "none" }}
|
||||||
|
@ -39,6 +39,7 @@ const DialogContent = React.forwardRef<
|
|||||||
"fixed left-[50%] top-[50%] z-50 flex flex-col w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
"fixed left-[50%] top-[50%] z-50 flex flex-col w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
203
web_app/src/components/ui/dropdown-menu.tsx
Normal file
203
web_app/src/components/ui/dropdown-menu.tsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotFilledIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||||
|
|
||||||
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
||||||
|
|
||||||
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||||
|
|
||||||
|
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||||
|
|
||||||
|
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||||
|
|
||||||
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||||
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
DropdownMenuSubTrigger.displayName =
|
||||||
|
DropdownMenuPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuSubContent.displayName =
|
||||||
|
DropdownMenuPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const DropdownMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Portal>
|
||||||
|
<DropdownMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
|
))
|
||||||
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DropdownMenuItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
DropdownMenuCheckboxItem.displayName =
|
||||||
|
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const DropdownMenuRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const DropdownMenuLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const DropdownMenuSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const DropdownMenuShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
}
|
@ -26,6 +26,11 @@
|
|||||||
transform: scale(1.03);
|
transform: scale(1.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.icon-button-icon-wrapper svg {
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import { Options, useHotkeys } from "react-hotkeys-hook"
|
|
||||||
import { useRecoilValue } from "recoil"
|
|
||||||
import { appState } from "@/lib/store"
|
|
||||||
|
|
||||||
const useHotKey = (
|
|
||||||
keys: string,
|
|
||||||
callback: any,
|
|
||||||
options?: Options,
|
|
||||||
deps?: any[]
|
|
||||||
) => {
|
|
||||||
const app = useRecoilValue(appState)
|
|
||||||
|
|
||||||
const ref = useHotkeys(
|
|
||||||
keys,
|
|
||||||
callback,
|
|
||||||
{ ...options, enabled: !app.disableShortCuts },
|
|
||||||
deps
|
|
||||||
)
|
|
||||||
return ref
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useHotKey
|
|
@ -1,11 +1,11 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
function useImage(file?: File): [HTMLImageElement, boolean] {
|
function useImage(file: File | null): [HTMLImageElement, boolean] {
|
||||||
const [image] = useState(new Image())
|
const [image] = useState(new Image())
|
||||||
const [isLoaded, setIsLoaded] = useState(false)
|
const [isLoaded, setIsLoaded] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (file === undefined) {
|
if (!file) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
|
@ -2,7 +2,7 @@ import { API_ENDPOINT } from "@/lib/api"
|
|||||||
import { useCallback, useEffect, useState } from "react"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
|
||||||
export default function useInputImage() {
|
export default function useInputImage() {
|
||||||
const [inputImage, setInputImage] = useState<File>()
|
const [inputImage, setInputImage] = useState<File | null>(null)
|
||||||
|
|
||||||
const fetchInputImage = useCallback(() => {
|
const fetchInputImage = useCallback(() => {
|
||||||
const headers = new Headers()
|
const headers = new Headers()
|
||||||
|
2
web_app/src/lib/const.ts
Normal file
2
web_app/src/lib/const.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const ACCENT_COLOR = "#ffcc00bb"
|
||||||
|
export const DEFAULT_BRUSH_SIZE = 40
|
118
web_app/src/lib/states.ts
Normal file
118
web_app/src/lib/states.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { create, StoreApi, UseBoundStore } from "zustand"
|
||||||
|
import { persist } from "zustand/middleware"
|
||||||
|
import { immer } from "zustand/middleware/immer"
|
||||||
|
import { SortBy, SortOrder } from "./types"
|
||||||
|
import { DEFAULT_BRUSH_SIZE } from "./const"
|
||||||
|
|
||||||
|
type FileManagerState = {
|
||||||
|
sortBy: SortBy
|
||||||
|
sortOrder: SortOrder
|
||||||
|
layout: "rows" | "masonry"
|
||||||
|
searchText: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppState = {
|
||||||
|
file: File | null
|
||||||
|
imageHeight: number
|
||||||
|
imageWidth: number
|
||||||
|
brushSize: number
|
||||||
|
brushSizeScale: number
|
||||||
|
|
||||||
|
isInpainting: boolean
|
||||||
|
isInteractiveSeg: boolean // 是否正处于 sam 状态
|
||||||
|
isInteractiveSegRunning: boolean
|
||||||
|
interactiveSegClicks: number[][]
|
||||||
|
|
||||||
|
prompt: string
|
||||||
|
|
||||||
|
fileManagerState: FileManagerState
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppAction = {
|
||||||
|
setFile: (file: File) => void
|
||||||
|
setIsInpainting: (newValue: boolean) => void
|
||||||
|
setBrushSize: (newValue: number) => void
|
||||||
|
setImageSize: (width: number, height: number) => void
|
||||||
|
setFileManagerSortBy: (newValue: SortBy) => void
|
||||||
|
setFileManagerSortOrder: (newValue: SortOrder) => void
|
||||||
|
setFileManagerLayout: (
|
||||||
|
newValue: AppState["fileManagerState"]["layout"]
|
||||||
|
) => void
|
||||||
|
setFileManagerSearchText: (newValue: string) => void
|
||||||
|
setPrompt: (newValue: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStore = create<AppState & AppAction>()(
|
||||||
|
immer(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
file: null,
|
||||||
|
imageHeight: 0,
|
||||||
|
imageWidth: 0,
|
||||||
|
brushSize: DEFAULT_BRUSH_SIZE,
|
||||||
|
brushSizeScale: 1,
|
||||||
|
isInpainting: false,
|
||||||
|
isInteractiveSeg: false,
|
||||||
|
isInteractiveSegRunning: false,
|
||||||
|
interactiveSegClicks: [],
|
||||||
|
prompt: "",
|
||||||
|
fileManagerState: {
|
||||||
|
sortBy: SortBy.CTIME,
|
||||||
|
sortOrder: SortOrder.DESCENDING,
|
||||||
|
layout: "masonry",
|
||||||
|
searchText: "",
|
||||||
|
},
|
||||||
|
setIsInpainting: (newValue: boolean) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.isInpainting = newValue
|
||||||
|
}),
|
||||||
|
setFile: (file: File) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
// TODO: 清空各种状态
|
||||||
|
state.file = file
|
||||||
|
}),
|
||||||
|
setBrushSize: (newValue: number) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.brushSize = newValue
|
||||||
|
}),
|
||||||
|
setImageSize: (width: number, height: number) => {
|
||||||
|
// 根据图片尺寸调整 brushSize 的 scale
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.imageWidth = width
|
||||||
|
state.imageHeight = height
|
||||||
|
state.brushSizeScale = Math.max(Math.min(width, height), 512) / 512
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setPrompt: (newValue: string) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.prompt = newValue
|
||||||
|
}),
|
||||||
|
setFileManagerSortBy: (newValue: SortBy) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.fileManagerState.sortBy = newValue
|
||||||
|
}),
|
||||||
|
setFileManagerSortOrder: (newValue: SortOrder) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.fileManagerState.sortOrder = newValue
|
||||||
|
}),
|
||||||
|
setFileManagerLayout: (newValue: "rows" | "masonry") =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.fileManagerState.layout = newValue
|
||||||
|
}),
|
||||||
|
setFileManagerSearchText: (newValue: string) =>
|
||||||
|
set((state: AppState) => {
|
||||||
|
state.fileManagerState.searchText = newValue
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "ZUSTAND_STATE", // name of the item in the storage (must be unique)
|
||||||
|
partialize: (state) =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(state).filter(([key]) =>
|
||||||
|
["fileManagerState", "prompt"].includes(key)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
@ -1,23 +1,6 @@
|
|||||||
import { atom, selector } from "recoil"
|
import { atom, selector } from "recoil"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
|
import { CV2Flag, HDStrategy, LDMSampler, ModelsHDSettings } from "./types"
|
||||||
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 {
|
export enum AIModel {
|
||||||
LAMA = "lama",
|
LAMA = "lama",
|
||||||
@ -75,18 +58,12 @@ export interface Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
file: File | undefined
|
|
||||||
imageHeight: number
|
|
||||||
imageWidth: number
|
|
||||||
disableShortCuts: boolean
|
|
||||||
isInpainting: boolean
|
|
||||||
isDisableModelSwitch: boolean
|
isDisableModelSwitch: boolean
|
||||||
isEnableAutoSaving: boolean
|
isEnableAutoSaving: boolean
|
||||||
isInteractiveSeg: boolean
|
isInteractiveSeg: boolean
|
||||||
isInteractiveSegRunning: boolean
|
isInteractiveSegRunning: boolean
|
||||||
interactiveSegClicks: number[][]
|
interactiveSegClicks: number[][]
|
||||||
enableFileManager: boolean
|
enableFileManager: boolean
|
||||||
brushSize: number
|
|
||||||
isControlNet: boolean
|
isControlNet: boolean
|
||||||
controlNetMethod: string
|
controlNetMethod: string
|
||||||
plugins: string[]
|
plugins: string[]
|
||||||
@ -96,18 +73,12 @@ interface AppState {
|
|||||||
export const appState = atom<AppState>({
|
export const appState = atom<AppState>({
|
||||||
key: "appState",
|
key: "appState",
|
||||||
default: {
|
default: {
|
||||||
file: undefined,
|
|
||||||
imageHeight: 0,
|
|
||||||
imageWidth: 0,
|
|
||||||
disableShortCuts: false,
|
|
||||||
isInpainting: false,
|
|
||||||
isDisableModelSwitch: false,
|
isDisableModelSwitch: false,
|
||||||
isEnableAutoSaving: false,
|
isEnableAutoSaving: false,
|
||||||
isInteractiveSeg: false,
|
isInteractiveSeg: false,
|
||||||
isInteractiveSegRunning: false,
|
isInteractiveSegRunning: false,
|
||||||
interactiveSegClicks: [],
|
interactiveSegClicks: [],
|
||||||
enableFileManager: false,
|
enableFileManager: false,
|
||||||
brushSize: 40,
|
|
||||||
isControlNet: false,
|
isControlNet: false,
|
||||||
controlNetMethod: ControlNetMethod.canny,
|
controlNetMethod: ControlNetMethod.canny,
|
||||||
plugins: [],
|
plugins: [],
|
||||||
@ -115,28 +86,11 @@ export const appState = atom<AppState>({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const propmtState = atom<string>({
|
|
||||||
key: "promptState",
|
|
||||||
default: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
export const negativePropmtState = atom<string>({
|
export const negativePropmtState = atom<string>({
|
||||||
key: "negativePromptState",
|
key: "negativePromptState",
|
||||||
default: "",
|
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({
|
export const isPluginRunningState = selector({
|
||||||
key: "isPluginRunningState",
|
key: "isPluginRunningState",
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
@ -175,42 +129,6 @@ export const serverConfigState = selector({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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({
|
export const enableFileManagerState = selector({
|
||||||
key: "enableFileManagerState",
|
key: "enableFileManagerState",
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
@ -223,30 +141,6 @@ export const enableFileManagerState = selector({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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({
|
export const isInteractiveSegState = selector({
|
||||||
key: "isInteractiveSegState",
|
key: "isInteractiveSegState",
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
@ -275,9 +169,7 @@ export const isProcessingState = selector({
|
|||||||
key: "isProcessingState",
|
key: "isProcessingState",
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
const app = get(appState)
|
const app = get(appState)
|
||||||
return (
|
return app.isInteractiveSegRunning || app.isPluginRunning
|
||||||
app.isInteractiveSegRunning || app.isPluginRunning || app.isInpainting
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -339,20 +231,6 @@ export const croperState = atom<Rect>({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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({
|
export const croperX = selector({
|
||||||
key: "croperX",
|
key: "croperX",
|
||||||
get: ({ get }) => get(croperState).x,
|
get: ({ get }) => get(croperState).x,
|
||||||
@ -435,43 +313,6 @@ export const extenderWidth = selector({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
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 {
|
export interface Settings {
|
||||||
show: boolean
|
show: boolean
|
||||||
showCroper: boolean
|
showCroper: boolean
|
||||||
@ -490,7 +331,6 @@ export interface Settings {
|
|||||||
|
|
||||||
// For SD
|
// For SD
|
||||||
sdMaskBlur: number
|
sdMaskBlur: number
|
||||||
sdMode: SDMode
|
|
||||||
sdStrength: number
|
sdStrength: number
|
||||||
sdSteps: number
|
sdSteps: number
|
||||||
sdGuidanceScale: number
|
sdGuidanceScale: number
|
||||||
@ -656,7 +496,6 @@ export const settingStateDefault: Settings = {
|
|||||||
|
|
||||||
// SD
|
// SD
|
||||||
sdMaskBlur: 5,
|
sdMaskBlur: 5,
|
||||||
sdMode: SDMode.inpainting,
|
|
||||||
sdStrength: 0.75,
|
sdStrength: 0.75,
|
||||||
sdSteps: 50,
|
sdSteps: 50,
|
||||||
sdGuidanceScale: 7.5,
|
sdGuidanceScale: 7.5,
|
||||||
@ -819,70 +658,3 @@ export const isDiffusionModelsState = selector({
|
|||||||
return isSD || isPaintByExample || isPix2Pix
|
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 })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
@ -6,3 +6,40 @@ export enum PluginName {
|
|||||||
RestoreFormer = "RestoreFormer",
|
RestoreFormer = "RestoreFormer",
|
||||||
InteractiveSeg = "InteractiveSeg",
|
InteractiveSeg = "InteractiveSeg",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SortBy {
|
||||||
|
NAME = "name",
|
||||||
|
CTIME = "ctime",
|
||||||
|
MTIME = "mtime",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SortOrder {
|
||||||
|
DESCENDING = "desc",
|
||||||
|
ASCENDING = "asc",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HDStrategy {
|
||||||
|
ORIGINAL = "Original",
|
||||||
|
RESIZE = "Resize",
|
||||||
|
CROP = "Crop",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LDMSampler {
|
||||||
|
ddim = "ddim",
|
||||||
|
plms = "plms",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CV2Flag {
|
||||||
|
INPAINT_NS = "INPAINT_NS",
|
||||||
|
INPAINT_TELEA = "INPAINT_TELEA",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HDSettings {
|
||||||
|
hdStrategy: HDStrategy
|
||||||
|
hdStrategyResizeLimit: number
|
||||||
|
hdStrategyCropTrigerSize: number
|
||||||
|
hdStrategyCropMargin: number
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModelsHDSettings = { [key in AIModel]: HDSettings }
|
||||||
|
Loading…
Reference in New Issue
Block a user