update
This commit is contained in:
@@ -3,7 +3,7 @@ import { nanoid } from "nanoid"
|
||||
|
||||
import useInputImage from "@/hooks/useInputImage"
|
||||
import { keepGUIAlive } from "@/lib/utils"
|
||||
import { getServerConfig, isDesktop } from "@/lib/api"
|
||||
import { getServerConfig } from "@/lib/api"
|
||||
import Header from "@/components/Header"
|
||||
import Workspace from "@/components/Workspace"
|
||||
import FileSelect from "@/components/FileSelect"
|
||||
@@ -40,21 +40,14 @@ function Home() {
|
||||
updateAppState({ windowSize })
|
||||
}, [windowSize])
|
||||
|
||||
// Keeping GUI Window Open
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const isRunDesktop = await isDesktop().then((res) => res.text())
|
||||
if (isRunDesktop === "True") {
|
||||
keepGUIAlive()
|
||||
}
|
||||
}
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchServerConfig = async () => {
|
||||
const serverConfig = await getServerConfig().then((res) => res.json())
|
||||
setServerConfig(serverConfig)
|
||||
if (serverConfig.isDesktop) {
|
||||
// Keeping GUI Window Open
|
||||
keepGUIAlive()
|
||||
}
|
||||
}
|
||||
fetchServerConfig()
|
||||
}, [])
|
||||
|
||||
@@ -382,6 +382,9 @@ export default function Editor(props: EditorProps) {
|
||||
}
|
||||
|
||||
const onPointerUp = (ev: SyntheticEvent) => {
|
||||
if (!hadDrawSomething()) {
|
||||
return
|
||||
}
|
||||
if (isMidClick(ev)) {
|
||||
setIsPanning(false)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IconButton } from "@/components/ui/button"
|
||||
import { useToggle } from "@uidotdev/usehooks"
|
||||
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "./ui/dialog"
|
||||
import { Info, Settings } from "lucide-react"
|
||||
import { HelpCircle, Settings } from "lucide-react"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Switch } from "./ui/switch"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
||||
import { useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { fetchModelInfos, switchModel } from "@/lib/api"
|
||||
@@ -30,7 +30,6 @@ import { useToast } from "./ui/use-toast"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogHeader,
|
||||
} from "./ui/alert-dialog"
|
||||
import {
|
||||
@@ -85,6 +84,9 @@ export function SettingsDialog() {
|
||||
])
|
||||
const { toast } = useToast()
|
||||
const [model, setModel] = useState<ModelInfo>(settings.model)
|
||||
useEffect(() => {
|
||||
setModel(settings.model)
|
||||
}, [settings.model])
|
||||
|
||||
const { data: modelInfos, status } = useQuery({
|
||||
queryKey: ["modelInfos"],
|
||||
@@ -163,7 +165,6 @@ export function SettingsDialog() {
|
||||
}
|
||||
|
||||
function onModelSelect(info: ModelInfo) {
|
||||
console.log(info)
|
||||
setModel(info)
|
||||
}
|
||||
|
||||
@@ -211,35 +212,35 @@ export function SettingsDialog() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 w-[510px]">
|
||||
<div className="flex flex-col gap-4 rounded-md">
|
||||
<div>Current Model</div>
|
||||
<div className="font-medium">Current Model</div>
|
||||
<div>{model.name}</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-4 rounded-md">
|
||||
<div className="flex gap-4 items-center justify-start">
|
||||
<div>Available models</div>
|
||||
<IconButton tooltip="How to download new model" asChild>
|
||||
<Info />
|
||||
</IconButton>
|
||||
<div className="flex gap-1 items-center justify-start">
|
||||
<div className="font-medium">Available models</div>
|
||||
{/* <IconButton tooltip="How to download new model" asChild>
|
||||
<HelpCircle size={16} strokeWidth={1.5} className="opacity-50" />
|
||||
</IconButton> */}
|
||||
</div>
|
||||
<Tabs defaultValue={defaultTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value={MODEL_TYPE_INPAINT}>Inpaint</TabsTrigger>
|
||||
<TabsTrigger value={MODEL_TYPE_INPAINT}>Erase</TabsTrigger>
|
||||
<TabsTrigger value={MODEL_TYPE_DIFFUSERS_SD}>
|
||||
Diffusion
|
||||
Stable Diffusion
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={MODEL_TYPE_DIFFUSERS_SD_INPAINT}>
|
||||
Diffusion inpaint
|
||||
Stable Diffusion Inpaint
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value={MODEL_TYPE_OTHER}>
|
||||
Diffusion other
|
||||
Other Diffusion
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<ScrollArea className="h-[240px] w-full mt-2 outline-none">
|
||||
<ScrollArea className="h-[240px] w-full mt-2 outline-none border rounded-lg">
|
||||
<TabsContent value={MODEL_TYPE_INPAINT}>
|
||||
{renderModelList([MODEL_TYPE_INPAINT])}
|
||||
</TabsContent>
|
||||
@@ -267,7 +268,7 @@ export function SettingsDialog() {
|
||||
|
||||
function renderGeneralSettings() {
|
||||
return (
|
||||
<div className="space-y-4 w-[400px]">
|
||||
<div className="space-y-4 w-[510px]">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enableManualInpainting"
|
||||
@@ -276,7 +277,8 @@ export function SettingsDialog() {
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Enable manual inpainting</FormLabel>
|
||||
<FormDescription>
|
||||
Click a button to trigger inpainting after draw mask.
|
||||
For erase model, click a button to trigger inpainting after
|
||||
draw mask.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
@@ -468,7 +470,7 @@ export function SettingsDialog() {
|
||||
</div>
|
||||
<Separator orientation="vertical" />
|
||||
<Form {...form}>
|
||||
<div className="flex w-full justify-center">
|
||||
<div className="flex w-full justify-center">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
{tab === TAB_MODEL ? renderModelSettings() : <></>}
|
||||
{tab === TAB_GENERAL ? renderGeneralSettings() : <></>}
|
||||
|
||||
77
web_app/src/components/SidePanel/CV2Options.tsx
Normal file
77
web_app/src/components/SidePanel/CV2Options.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useStore } from "@/lib/states"
|
||||
import { LabelTitle, RowContainer } from "./LabelTitle"
|
||||
import { NumberInput } from "../ui/input"
|
||||
import { Slider } from "../ui/slider"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select"
|
||||
import { CV2Flag } from "@/lib/types"
|
||||
|
||||
const CV2Options = () => {
|
||||
const [settings, updateSettings] = useStore((state) => [
|
||||
state.settings,
|
||||
state.updateSettings,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
<RowContainer>
|
||||
<LabelTitle
|
||||
text="CV2 Flag"
|
||||
url="https://docs.opencv.org/4.8.0/d7/d8b/group__photo__inpaint.html#gga8002a65f5a3328fbf15df81b842d3c3ca892824c38e258feb5e72f308a358d52e"
|
||||
/>
|
||||
<Select
|
||||
value={settings.cv2Flag as string}
|
||||
onValueChange={(value) => {
|
||||
const flag = value as CV2Flag
|
||||
updateSettings({ cv2Flag: flag })
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[160px]">
|
||||
<SelectValue placeholder="Select flag" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectGroup>
|
||||
{Object.values(CV2Flag).map((flag) => (
|
||||
<SelectItem key={flag as string} value={flag as string}>
|
||||
{flag as string}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</RowContainer>
|
||||
<LabelTitle
|
||||
text="CV2 Radius"
|
||||
url="https://docs.opencv.org/4.8.0/d7/d8b/group__photo__inpaint.html#gga8002a65f5a3328fbf15df81b842d3c3ca892824c38e258feb5e72f308a358d52e"
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[5]}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[Math.floor(settings.cv2Radius)]}
|
||||
onValueChange={(vals) => updateSettings({ cv2Radius: vals[0] })}
|
||||
/>
|
||||
<NumberInput
|
||||
id="cv2-radius"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.cv2Radius}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ cv2Radius: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CV2Options
|
||||
@@ -1,9 +1,7 @@
|
||||
import { FormEvent, useState } from "react"
|
||||
import { useToggle } from "react-use"
|
||||
import { FormEvent } from "react"
|
||||
import { useStore } from "@/lib/states"
|
||||
import { Switch } from "./ui/switch"
|
||||
import { Label } from "./ui/label"
|
||||
import { NumberInput } from "./ui/input"
|
||||
import { Switch } from "../ui/switch"
|
||||
import { NumberInput } from "../ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -11,56 +9,28 @@ import {
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./ui/select"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
} from "../ui/select"
|
||||
import { Textarea } from "../ui/textarea"
|
||||
import { SDSampler } from "@/lib/types"
|
||||
import { Separator } from "./ui/separator"
|
||||
import { ScrollArea } from "./ui/scroll-area"
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTrigger } from "./ui/sheet"
|
||||
import {
|
||||
ArrowDownFromLine,
|
||||
ArrowLeftFromLine,
|
||||
ArrowRightFromLine,
|
||||
ArrowUpFromLine,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
HelpCircle,
|
||||
LucideIcon,
|
||||
Maximize,
|
||||
Move,
|
||||
MoveHorizontal,
|
||||
MoveVertical,
|
||||
Upload,
|
||||
} from "lucide-react"
|
||||
import { Button, ImageUploadButton } from "./ui/button"
|
||||
import useHotKey from "@/hooks/useHotkey"
|
||||
import { Slider } from "./ui/slider"
|
||||
import { Separator } from "../ui/separator"
|
||||
import { Move, MoveHorizontal, MoveVertical, Upload } from "lucide-react"
|
||||
import { Button, ImageUploadButton } from "../ui/button"
|
||||
import { Slider } from "../ui/slider"
|
||||
import { useImage } from "@/hooks/useImage"
|
||||
import {
|
||||
EXTENDER_ALL,
|
||||
EXTENDER_BUILTIN_ALL,
|
||||
EXTENDER_BUILTIN_X_LEFT,
|
||||
EXTENDER_BUILTIN_X_RIGHT,
|
||||
EXTENDER_BUILTIN_Y_BOTTOM,
|
||||
EXTENDER_BUILTIN_Y_TOP,
|
||||
EXTENDER_X,
|
||||
EXTENDER_Y,
|
||||
INSTRUCT_PIX2PIX,
|
||||
PAINT_BY_EXAMPLE,
|
||||
} from "@/lib/const"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"
|
||||
|
||||
const RowContainer = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="flex justify-between items-center pr-2">{children}</div>
|
||||
)
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
|
||||
import { RowContainer, LabelTitle } from "./LabelTitle"
|
||||
|
||||
const ExtenderButton = ({
|
||||
IconCls,
|
||||
text,
|
||||
onClick,
|
||||
}: {
|
||||
IconCls: LucideIcon
|
||||
text: string
|
||||
onClick: () => void
|
||||
}) => {
|
||||
@@ -73,92 +43,32 @@ const ExtenderButton = ({
|
||||
disabled={!showExtender}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<IconCls size={15} strokeWidth={1} />
|
||||
{text}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">{text}</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const LabelTitle = ({
|
||||
text,
|
||||
toolTip,
|
||||
url,
|
||||
htmlFor,
|
||||
disabled = false,
|
||||
}: {
|
||||
text: string
|
||||
toolTip?: string
|
||||
url?: string
|
||||
htmlFor?: string
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label
|
||||
htmlFor={htmlFor ? htmlFor : text.toLowerCase().replace(" ", "-")}
|
||||
className="font-medium"
|
||||
disabled={disabled}
|
||||
>
|
||||
{text}
|
||||
</Label>
|
||||
</TooltipTrigger>
|
||||
{toolTip ? (
|
||||
<TooltipContent className="flex flex-col max-w-xs text-sm" side="left">
|
||||
<p>{toolTip}</p>
|
||||
{url ? (
|
||||
<Button variant="link" className="justify-end">
|
||||
<a href={url} target="_blank">
|
||||
More info
|
||||
</a>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</TooltipContent>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const SidePanel = () => {
|
||||
const DiffusionOptions = () => {
|
||||
const [
|
||||
settings,
|
||||
windowSize,
|
||||
paintByExampleFile,
|
||||
isProcessing,
|
||||
updateSettings,
|
||||
showSidePanel,
|
||||
runInpainting,
|
||||
updateAppState,
|
||||
updateExtenderByBuiltIn,
|
||||
updateExtenderDirection,
|
||||
] = useStore((state) => [
|
||||
state.settings,
|
||||
state.windowSize,
|
||||
state.paintByExampleFile,
|
||||
state.getIsProcessing(),
|
||||
state.updateSettings,
|
||||
state.showSidePanel(),
|
||||
state.runInpainting,
|
||||
state.updateAppState,
|
||||
state.updateExtenderByBuiltIn,
|
||||
state.updateExtenderDirection,
|
||||
])
|
||||
const [exampleImage, isExampleImageLoaded] = useImage(paintByExampleFile)
|
||||
const [open, toggleOpen] = useToggle(true)
|
||||
|
||||
useHotKey("c", () => {
|
||||
toggleOpen()
|
||||
})
|
||||
|
||||
if (!showSidePanel) {
|
||||
return null
|
||||
}
|
||||
|
||||
const onKeyUp = (e: React.KeyboardEvent) => {
|
||||
// negativePrompt 回车触发 inpainting
|
||||
@@ -582,32 +492,20 @@ const SidePanel = () => {
|
||||
className="flex gap-2 justify-center mt-0"
|
||||
>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowLeftFromLine}
|
||||
text="1.25x"
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 1.25)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
text="1.5x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_LEFT, 1.5)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 1.5)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
text="1.75x"
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 1.75)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowLeftFromLine}
|
||||
text="2.0x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_LEFT, 2.0)
|
||||
}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowRightFromLine}
|
||||
text="1.5x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_RIGHT, 1.5)
|
||||
}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowRightFromLine}
|
||||
text="2.0x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_X_RIGHT, 2.0)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_X, 2.0)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
@@ -615,32 +513,20 @@ const SidePanel = () => {
|
||||
className="flex gap-2 justify-center mt-0"
|
||||
>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowUpFromLine}
|
||||
text="1.25x"
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 1.25)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
text="1.5x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_TOP, 1.5)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 1.5)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
text="1.75x"
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 1.75)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowUpFromLine}
|
||||
text="2.0x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_TOP, 2.0)
|
||||
}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowDownFromLine}
|
||||
text="1.5x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_BOTTOM, 1.5)
|
||||
}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={ArrowDownFromLine}
|
||||
text="2.0x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_Y_BOTTOM, 2.0)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_Y, 2.0)}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
@@ -648,32 +534,20 @@ const SidePanel = () => {
|
||||
className="flex gap-2 justify-center mt-0"
|
||||
>
|
||||
<ExtenderButton
|
||||
IconCls={Maximize}
|
||||
text="1.25x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 1.25)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 1.25)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={Maximize}
|
||||
text="1.5x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 1.5)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 1.5)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={Maximize}
|
||||
text="1.75x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 1.75)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 1.75)}
|
||||
/>
|
||||
<ExtenderButton
|
||||
IconCls={Maximize}
|
||||
text="2.0x"
|
||||
onClick={() =>
|
||||
updateExtenderByBuiltIn(EXTENDER_BUILTIN_ALL, 2.0)
|
||||
}
|
||||
onClick={() => updateExtenderByBuiltIn(EXTENDER_ALL, 2.0)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
@@ -684,247 +558,194 @@ const SidePanel = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} modal={false}>
|
||||
<SheetTrigger
|
||||
tabIndex={-1}
|
||||
className="z-10 outline-none absolute top-[68px] right-6 rounded-lg border bg-background"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
asChild
|
||||
className="p-1.5"
|
||||
onClick={toggleOpen}
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
<RowContainer>
|
||||
<LabelTitle
|
||||
text="Cropper"
|
||||
toolTip="Inpainting on part of image, improve inference speed and reduce memory usage."
|
||||
/>
|
||||
<Switch
|
||||
id="cropper"
|
||||
checked={settings.showCropper}
|
||||
onCheckedChange={(value) => {
|
||||
updateSettings({ showCropper: value })
|
||||
if (value) {
|
||||
updateSettings({ showExtender: false })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
|
||||
{renderExtender()}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
htmlFor="steps"
|
||||
text="Steps"
|
||||
toolTip="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[30]}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[Math.floor(settings.sdSteps)]}
|
||||
onValueChange={(vals) => updateSettings({ sdSteps: vals[0] })}
|
||||
/>
|
||||
<NumberInput
|
||||
id="steps"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.sdSteps}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ sdSteps: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
text="Guidance scale"
|
||||
url="https://huggingface.co/docs/diffusers/main/en/using-diffusers/inpaint#guidance-scale"
|
||||
toolTip="Guidance scale affects how aligned the text prompt and generated image are. Higher value means the prompt and generated image are closely aligned, so the output is a stricter interpretation of the prompt"
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[750]}
|
||||
min={0}
|
||||
max={1500}
|
||||
step={1}
|
||||
value={[Math.floor(settings.sdGuidanceScale * 100)]}
|
||||
onValueChange={(vals) =>
|
||||
updateSettings({ sdGuidanceScale: vals[0] / 100 })
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
id="guidance-scale"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.sdGuidanceScale}
|
||||
allowFloat
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ sdGuidanceScale: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
|
||||
{renderP2PImageGuidanceScale()}
|
||||
{renderStrength()}
|
||||
|
||||
<RowContainer>
|
||||
<LabelTitle text="Sampler" />
|
||||
<Select
|
||||
value={settings.sdSampler as string}
|
||||
onValueChange={(value) => {
|
||||
const sampler = value as SDSampler
|
||||
updateSettings({ sdSampler: sampler })
|
||||
}}
|
||||
>
|
||||
<ChevronLeft strokeWidth={1} />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
side="right"
|
||||
className="w-[300px] mt-[60px] outline-none pl-4 pr-1"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onPointerDownOutside={(event) => event.preventDefault()}
|
||||
>
|
||||
<SheetHeader>
|
||||
<RowContainer>
|
||||
<div className="overflow-hidden mr-8">
|
||||
{
|
||||
settings.model.name.split("/")[
|
||||
settings.model.name.split("/").length - 1
|
||||
]
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="border h-6 w-6"
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<ChevronRight strokeWidth={1} />
|
||||
</Button>
|
||||
</RowContainer>
|
||||
<Separator />
|
||||
</SheetHeader>
|
||||
<ScrollArea
|
||||
style={{ height: windowSize.height - 160 }}
|
||||
className="pr-3"
|
||||
>
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
<RowContainer>
|
||||
<LabelTitle
|
||||
text="Cropper"
|
||||
toolTip="Inpainting on part of image, improve inference speed and reduce memory usage."
|
||||
/>
|
||||
<Switch
|
||||
id="cropper"
|
||||
checked={settings.showCropper}
|
||||
onCheckedChange={(value) => {
|
||||
updateSettings({ showCropper: value })
|
||||
if (value) {
|
||||
updateSettings({ showExtender: false })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
<SelectTrigger className="w-[100px]">
|
||||
<SelectValue placeholder="Select sampler" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectGroup>
|
||||
{Object.values(SDSampler).map((sampler) => (
|
||||
<SelectItem key={sampler as string} value={sampler as string}>
|
||||
{sampler as string}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</RowContainer>
|
||||
|
||||
{renderExtender()}
|
||||
<RowContainer>
|
||||
{/* 每次会从服务器返回更新该值 */}
|
||||
<LabelTitle
|
||||
text="Seed"
|
||||
toolTip="Using same parameters and a fixed seed can generate same result image."
|
||||
/>
|
||||
{/* <Pin /> */}
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<Switch
|
||||
id="seed"
|
||||
checked={settings.seedFixed}
|
||||
onCheckedChange={(value) => {
|
||||
updateSettings({ seedFixed: value })
|
||||
}}
|
||||
/>
|
||||
<NumberInput
|
||||
id="seed"
|
||||
className="w-[100px]"
|
||||
disabled={!settings.seedFixed}
|
||||
numberValue={settings.seed}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ seed: val })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</RowContainer>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
htmlFor="steps"
|
||||
text="Steps"
|
||||
toolTip="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[30]}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[Math.floor(settings.sdSteps)]}
|
||||
onValueChange={(vals) => updateSettings({ sdSteps: vals[0] })}
|
||||
/>
|
||||
<NumberInput
|
||||
id="steps"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.sdSteps}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ sdSteps: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
{renderNegativePrompt()}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
text="Guidance scale"
|
||||
url="https://huggingface.co/docs/diffusers/main/en/using-diffusers/inpaint#guidance-scale"
|
||||
toolTip="Guidance scale affects how aligned the text prompt and generated image are. Higher value means the prompt and generated image are closely aligned, so the output is a stricter interpretation of the prompt"
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[750]}
|
||||
min={0}
|
||||
max={1500}
|
||||
step={1}
|
||||
value={[Math.floor(settings.sdGuidanceScale * 100)]}
|
||||
onValueChange={(vals) =>
|
||||
updateSettings({ sdGuidanceScale: vals[0] / 100 })
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
id="guidance-scale"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.sdGuidanceScale}
|
||||
allowFloat
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ sdGuidanceScale: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
{renderP2PImageGuidanceScale()}
|
||||
{renderStrength()}
|
||||
{renderConterNetSetting()}
|
||||
{renderFreeu()}
|
||||
{renderLCMLora()}
|
||||
|
||||
<RowContainer>
|
||||
<LabelTitle text="Sampler" />
|
||||
<Select
|
||||
value={settings.sdSampler as string}
|
||||
onValueChange={(value) => {
|
||||
const sampler = value as SDSampler
|
||||
updateSettings({ sdSampler: sampler })
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[100px]">
|
||||
<SelectValue placeholder="Select sampler" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectGroup>
|
||||
{Object.values(SDSampler).map((sampler) => (
|
||||
<SelectItem
|
||||
key={sampler as string}
|
||||
value={sampler as string}
|
||||
>
|
||||
{sampler as string}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</RowContainer>
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
text="Mask blur"
|
||||
toolTip="How much to blur the mask before processing, in pixels."
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[5]}
|
||||
min={0}
|
||||
max={35}
|
||||
step={1}
|
||||
value={[Math.floor(settings.sdMaskBlur)]}
|
||||
onValueChange={(vals) => updateSettings({ sdMaskBlur: vals[0] })}
|
||||
/>
|
||||
<NumberInput
|
||||
id="mask-blur"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.sdMaskBlur}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(value) => {
|
||||
updateSettings({ sdMaskBlur: value })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
|
||||
<RowContainer>
|
||||
{/* 每次会从服务器返回更新该值 */}
|
||||
<LabelTitle
|
||||
text="Seed"
|
||||
toolTip="Using same parameters and a fixed seed can generate same result image."
|
||||
/>
|
||||
{/* <Pin /> */}
|
||||
<div className="flex gap-2 justify-center items-center">
|
||||
<Switch
|
||||
id="seed"
|
||||
checked={settings.seedFixed}
|
||||
onCheckedChange={(value) => {
|
||||
updateSettings({ seedFixed: value })
|
||||
}}
|
||||
/>
|
||||
<NumberInput
|
||||
id="seed"
|
||||
className="w-[100px]"
|
||||
disabled={!settings.seedFixed}
|
||||
numberValue={settings.seed}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ seed: val })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</RowContainer>
|
||||
<RowContainer>
|
||||
<LabelTitle
|
||||
text="Match histograms"
|
||||
toolTip="Match the inpainting result histogram to the source image histogram"
|
||||
url="https://github.com/Sanster/lama-cleaner/pull/143#issuecomment-1325859307"
|
||||
/>
|
||||
<Switch
|
||||
id="match-histograms"
|
||||
checked={settings.sdMatchHistograms}
|
||||
onCheckedChange={(value) => {
|
||||
updateSettings({ sdMatchHistograms: value })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
|
||||
{renderNegativePrompt()}
|
||||
<Separator />
|
||||
|
||||
<Separator />
|
||||
|
||||
{renderConterNetSetting()}
|
||||
{renderFreeu()}
|
||||
{renderLCMLora()}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
text="Mask blur"
|
||||
toolTip="How much to blur the mask before processing, in pixels."
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[5]}
|
||||
min={0}
|
||||
max={35}
|
||||
step={1}
|
||||
value={[Math.floor(settings.sdMaskBlur)]}
|
||||
onValueChange={(vals) =>
|
||||
updateSettings({ sdMaskBlur: vals[0] })
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
id="mask-blur"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.sdMaskBlur}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(value) => {
|
||||
updateSettings({ sdMaskBlur: value })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
|
||||
<RowContainer>
|
||||
<LabelTitle
|
||||
text="Match histograms"
|
||||
toolTip="Match the inpainting result histogram to the source image histogram"
|
||||
url="https://github.com/Sanster/lama-cleaner/pull/143#issuecomment-1325859307"
|
||||
/>
|
||||
<Switch
|
||||
id="match-histograms"
|
||||
checked={settings.sdMatchHistograms}
|
||||
onCheckedChange={(value) => {
|
||||
updateSettings({ sdMatchHistograms: value })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
|
||||
<Separator />
|
||||
|
||||
{renderPaintByExample()}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
{renderPaintByExample()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidePanel
|
||||
export default DiffusionOptions
|
||||
77
web_app/src/components/SidePanel/LDMOptions.tsx
Normal file
77
web_app/src/components/SidePanel/LDMOptions.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useStore } from "@/lib/states"
|
||||
import { LabelTitle, RowContainer } from "./LabelTitle"
|
||||
import { NumberInput } from "../ui/input"
|
||||
import { Slider } from "../ui/slider"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select"
|
||||
import { LDMSampler } from "@/lib/types"
|
||||
|
||||
const LDMOptions = () => {
|
||||
const [settings, updateSettings] = useStore((state) => [
|
||||
state.settings,
|
||||
state.updateSettings,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
htmlFor="steps"
|
||||
text="Steps"
|
||||
toolTip="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[30]}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[Math.floor(settings.ldmSteps)]}
|
||||
onValueChange={(vals) => updateSettings({ ldmSteps: vals[0] })}
|
||||
/>
|
||||
<NumberInput
|
||||
id="steps"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.ldmSteps}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ ldmSteps: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
</div>
|
||||
<RowContainer>
|
||||
<LabelTitle text="Sampler" />
|
||||
<Select
|
||||
value={settings.ldmSampler as string}
|
||||
onValueChange={(value) => {
|
||||
const sampler = value as LDMSampler
|
||||
updateSettings({ ldmSampler: sampler })
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[100px]">
|
||||
<SelectValue placeholder="Select sampler" />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectGroup>
|
||||
{Object.values(LDMSampler).map((sampler) => (
|
||||
<SelectItem key={sampler as string} value={sampler as string}>
|
||||
{sampler as string}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</RowContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LDMOptions
|
||||
53
web_app/src/components/SidePanel/LabelTitle.tsx
Normal file
53
web_app/src/components/SidePanel/LabelTitle.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Button } from "../ui/button"
|
||||
import { Label } from "../ui/label"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"
|
||||
|
||||
const RowContainer = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="flex justify-between items-center pr-2">{children}</div>
|
||||
)
|
||||
|
||||
const LabelTitle = ({
|
||||
text,
|
||||
toolTip = "",
|
||||
url,
|
||||
htmlFor,
|
||||
disabled = false,
|
||||
}: {
|
||||
text: string
|
||||
toolTip?: string
|
||||
url?: string
|
||||
htmlFor?: string
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label
|
||||
htmlFor={htmlFor ? htmlFor : text.toLowerCase().replace(" ", "-")}
|
||||
className="font-medium"
|
||||
disabled={disabled}
|
||||
>
|
||||
{text}
|
||||
</Label>
|
||||
</TooltipTrigger>
|
||||
{toolTip || url ? (
|
||||
<TooltipContent className="flex flex-col max-w-xs text-sm" side="left">
|
||||
<p>{toolTip}</p>
|
||||
{url ? (
|
||||
<Button variant="link" className="justify-end">
|
||||
<a href={url} target="_blank">
|
||||
More info
|
||||
</a>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</TooltipContent>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export { LabelTitle, RowContainer }
|
||||
98
web_app/src/components/SidePanel/index.tsx
Normal file
98
web_app/src/components/SidePanel/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useToggle } from "react-use"
|
||||
import { useStore } from "@/lib/states"
|
||||
import { Separator } from "../ui/separator"
|
||||
import { ScrollArea } from "../ui/scroll-area"
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTrigger } from "../ui/sheet"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { Button } from "../ui/button"
|
||||
import useHotKey from "@/hooks/useHotkey"
|
||||
import { RowContainer } from "./LabelTitle"
|
||||
import { CV2, LDM, MODEL_TYPE_INPAINT } from "@/lib/const"
|
||||
import LDMOptions from "./LDMOptions"
|
||||
import DiffusionOptions from "./DiffusionOptions"
|
||||
import CV2Options from "./CV2Options"
|
||||
|
||||
const SidePanel = () => {
|
||||
const [settings, windowSize] = useStore((state) => [
|
||||
state.settings,
|
||||
state.windowSize,
|
||||
])
|
||||
|
||||
const [open, toggleOpen] = useToggle(true)
|
||||
|
||||
useHotKey("c", () => {
|
||||
toggleOpen()
|
||||
})
|
||||
|
||||
if (
|
||||
settings.model.name !== LDM &&
|
||||
settings.model.name !== CV2 &&
|
||||
settings.model.model_type === MODEL_TYPE_INPAINT
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const renderSidePanelOptions = () => {
|
||||
if (settings.model.name === LDM) {
|
||||
return <LDMOptions />
|
||||
}
|
||||
if (settings.model.name === CV2) {
|
||||
return <CV2Options />
|
||||
}
|
||||
return <DiffusionOptions />
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} modal={false}>
|
||||
<SheetTrigger
|
||||
tabIndex={-1}
|
||||
className="z-10 outline-none absolute top-[68px] right-6 rounded-lg border bg-background"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
asChild
|
||||
className="p-1.5"
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<ChevronLeft strokeWidth={1} />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
side="right"
|
||||
className="w-[300px] mt-[60px] outline-none pl-4 pr-1"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onPointerDownOutside={(event) => event.preventDefault()}
|
||||
>
|
||||
<SheetHeader>
|
||||
<RowContainer>
|
||||
<div className="overflow-hidden mr-8">
|
||||
{
|
||||
settings.model.name.split("/")[
|
||||
settings.model.name.split("/").length - 1
|
||||
]
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="border h-6 w-6"
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<ChevronRight strokeWidth={1} />
|
||||
</Button>
|
||||
</RowContainer>
|
||||
<Separator />
|
||||
</SheetHeader>
|
||||
<ScrollArea
|
||||
style={{ height: windowSize.height - 160 }}
|
||||
className="pr-3"
|
||||
>
|
||||
{renderSidePanelOptions()}
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidePanel
|
||||
@@ -1,23 +1,11 @@
|
||||
import { useEffect } from "react"
|
||||
import Editor from "./Editor"
|
||||
import {
|
||||
AIModel,
|
||||
isPaintByExampleState,
|
||||
isPix2PixState,
|
||||
isSDState,
|
||||
} from "@/lib/store"
|
||||
import { currentModel } from "@/lib/api"
|
||||
import { useStore } from "@/lib/states"
|
||||
import ImageSize from "./ImageSize"
|
||||
import Plugins from "./Plugins"
|
||||
import { InteractiveSeg } from "./InteractiveSeg"
|
||||
import SidePanel from "./SidePanel"
|
||||
// import SidePanel from "./SidePanel/SidePanel"
|
||||
// import PESidePanel from "./SidePanel/PESidePanel"
|
||||
// import P2PSidePanel from "./SidePanel/P2PSidePanel"
|
||||
// import Plugins from "./Plugins/Plugins"
|
||||
// import Flex from "./shared/Layout"
|
||||
// import ImageSize from "./ImageSize/ImageSize"
|
||||
|
||||
const Workspace = () => {
|
||||
const [file, updateSettings] = useStore((state) => [
|
||||
@@ -35,10 +23,6 @@ const Workspace = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* {isSD ? <SidePanel /> : <></>}
|
||||
{isPaintByExample ? <PESidePanel /> : <></>}
|
||||
{isPix2Pix ? <P2PSidePanel /> : <></>}
|
||||
{/* <SettingModal onClose={onSettingClose} /> */}
|
||||
<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
|
||||
<Plugins />
|
||||
<ImageSize />
|
||||
|
||||
@@ -125,12 +125,6 @@ export function fetchModelInfos(): Promise<ModelInfo[]> {
|
||||
return api.get("/models").then((response) => response.data)
|
||||
}
|
||||
|
||||
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",
|
||||
|
||||
@@ -11,12 +11,9 @@ export const BRUSH_COLOR = "#ffcc00bb"
|
||||
export const EXTENDER_X = "extender_x"
|
||||
export const EXTENDER_Y = "extender_y"
|
||||
export const EXTENDER_ALL = "extender_all"
|
||||
export const EXTENDER_BUILTIN_X_LEFT = "extender_builtin_x_left"
|
||||
export const EXTENDER_BUILTIN_X_RIGHT = "extender_builtin_x_right"
|
||||
export const EXTENDER_BUILTIN_Y_TOP = "extender_builtin_y_top"
|
||||
export const EXTENDER_BUILTIN_Y_BOTTOM = "extender_builtin_y_bottom"
|
||||
export const EXTENDER_BUILTIN_ALL = "extender_builtin_all"
|
||||
|
||||
export const LDM = "ldm"
|
||||
export const CV2 = "cv2"
|
||||
export const PAINT_BY_EXAMPLE = "Fantasy-Studio/Paint-by-Example"
|
||||
export const INSTRUCT_PIX2PIX = "timbrooks/instruct-pix2pix"
|
||||
export const KANDINSKY_2_2 = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
|
||||
|
||||
@@ -22,11 +22,6 @@ import {
|
||||
DEFAULT_BRUSH_SIZE,
|
||||
DEFAULT_NEGATIVE_PROMPT,
|
||||
EXTENDER_ALL,
|
||||
EXTENDER_BUILTIN_ALL,
|
||||
EXTENDER_BUILTIN_X_LEFT,
|
||||
EXTENDER_BUILTIN_X_RIGHT,
|
||||
EXTENDER_BUILTIN_Y_BOTTOM,
|
||||
EXTENDER_BUILTIN_Y_TOP,
|
||||
EXTENDER_X,
|
||||
EXTENDER_Y,
|
||||
MODEL_TYPE_INPAINT,
|
||||
@@ -112,6 +107,7 @@ type ServerConfig = {
|
||||
enableAutoSaving: boolean
|
||||
enableControlnet: boolean
|
||||
controlnetMethod: string
|
||||
isDesktop: boolean
|
||||
}
|
||||
|
||||
type InteractiveSegState = {
|
||||
@@ -129,6 +125,7 @@ type EditorState = {
|
||||
lineGroups: LineGroup[]
|
||||
lastLineGroup: LineGroup
|
||||
curLineGroup: LineGroup
|
||||
// 只用来显示
|
||||
extraMasks: HTMLImageElement[]
|
||||
// redo 相关
|
||||
redoRenders: HTMLImageElement[]
|
||||
@@ -153,7 +150,7 @@ type AppState = {
|
||||
|
||||
cropperState: CropperState
|
||||
extenderState: CropperState
|
||||
isCropperExtenderResizing: bool
|
||||
isCropperExtenderResizing: boolean
|
||||
|
||||
serverConfig: ServerConfig
|
||||
|
||||
@@ -194,7 +191,6 @@ type AppAction = {
|
||||
resetInteractiveSegState: () => void
|
||||
handleInteractiveSegAccept: () => void
|
||||
showPromptInput: () => boolean
|
||||
showSidePanel: () => boolean
|
||||
|
||||
runInpainting: () => Promise<void>
|
||||
showPrevMask: () => Promise<void>
|
||||
@@ -281,6 +277,7 @@ const defaultValues: AppState = {
|
||||
enableAutoSaving: false,
|
||||
enableControlnet: false,
|
||||
controlnetMethod: "lllyasviel/control_v11p_sd15_canny",
|
||||
isDesktop: false,
|
||||
},
|
||||
settings: {
|
||||
model: {
|
||||
@@ -334,6 +331,9 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
...defaultValues,
|
||||
|
||||
showPrevMask: async () => {
|
||||
if (get().settings.showExtender) {
|
||||
return
|
||||
}
|
||||
const { lastLineGroup, curLineGroup } = get().editorState
|
||||
const { prevInteractiveSegMask, interactiveSegMask } =
|
||||
get().interactiveSegState
|
||||
@@ -380,7 +380,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
}
|
||||
return targetFile
|
||||
},
|
||||
// todo: 传入 custom mask,单独逻辑
|
||||
|
||||
runInpainting: async () => {
|
||||
const {
|
||||
isInpainting,
|
||||
@@ -399,6 +399,14 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
if (file === null) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
settings.showExtender &&
|
||||
extenderState.height === imageHeight &&
|
||||
extenderState.width === imageWidth
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const { lastLineGroup, curLineGroup, lineGroups, renders } =
|
||||
get().editorState
|
||||
|
||||
@@ -406,42 +414,33 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
get().interactiveSegState
|
||||
|
||||
const useLastLineGroup =
|
||||
curLineGroup.length === 0 && interactiveSegMask === null
|
||||
|
||||
const maskImage = useLastLineGroup
|
||||
? prevInteractiveSegMask
|
||||
: interactiveSegMask
|
||||
curLineGroup.length === 0 &&
|
||||
interactiveSegMask === null &&
|
||||
!settings.showExtender
|
||||
|
||||
// useLastLineGroup 的影响
|
||||
// 1. 使用上一次的 mask
|
||||
// 2. 结果替换当前 render
|
||||
let maskImage = null
|
||||
let maskLineGroup: LineGroup = []
|
||||
if (useLastLineGroup === true) {
|
||||
if (
|
||||
lastLineGroup.length === 0 &&
|
||||
maskImage === null &&
|
||||
!settings.showExtender
|
||||
) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: "Please draw mask on picture",
|
||||
})
|
||||
return
|
||||
}
|
||||
maskLineGroup = lastLineGroup
|
||||
maskImage = prevInteractiveSegMask
|
||||
} else {
|
||||
if (
|
||||
curLineGroup.length === 0 &&
|
||||
maskImage === null &&
|
||||
!settings.showExtender
|
||||
) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: "Please draw mask on picture",
|
||||
})
|
||||
return
|
||||
}
|
||||
maskLineGroup = curLineGroup
|
||||
maskImage = interactiveSegMask
|
||||
}
|
||||
|
||||
if (
|
||||
maskLineGroup.length === 0 &&
|
||||
maskImage === null &&
|
||||
!settings.showExtender
|
||||
) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: "Please draw mask on picture",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const newLineGroups = [...lineGroups, maskLineGroup]
|
||||
@@ -498,6 +497,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
const newRender = new Image()
|
||||
await loadImage(newRender, blob)
|
||||
const newRenders = [...renders, newRender]
|
||||
get().setImageSize(newRender.width, newRender.height)
|
||||
get().updateEditorState({
|
||||
renders: newRenders,
|
||||
lineGroups: newLineGroups,
|
||||
@@ -545,7 +545,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
const { blob } = res
|
||||
const newRender = new Image()
|
||||
await loadImage(newRender, blob)
|
||||
get().setImageSize(newRender.height, newRender.width)
|
||||
get().setImageSize(newRender.width, newRender.height)
|
||||
const newRenders = [...renders, newRender]
|
||||
const newLineGroups = [...lineGroups, []]
|
||||
get().updateEditorState({
|
||||
@@ -739,11 +739,6 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
)
|
||||
},
|
||||
|
||||
showSidePanel: (): boolean => {
|
||||
const model = get().settings.model
|
||||
return model.model_type !== MODEL_TYPE_INPAINT
|
||||
},
|
||||
|
||||
setServerConfig: (newValue: ServerConfig) => {
|
||||
set((state) => {
|
||||
state.serverConfig = newValue
|
||||
@@ -910,6 +905,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
state.extenderState.width = state.imageWidth
|
||||
state.extenderState.height = state.imageHeight
|
||||
})
|
||||
get().updateExtenderByBuiltIn(newValue, 1.5)
|
||||
},
|
||||
|
||||
updateExtenderByBuiltIn: (direction: string, scale: number) => {
|
||||
@@ -920,21 +916,15 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
|
||||
height = imageHeight
|
||||
|
||||
switch (direction) {
|
||||
case EXTENDER_BUILTIN_X_LEFT:
|
||||
x = -Math.ceil(imageWidth * (scale - 1))
|
||||
case EXTENDER_X:
|
||||
x = -Math.ceil((imageWidth * (scale - 1)) / 2)
|
||||
width = Math.ceil(imageWidth * scale)
|
||||
break
|
||||
case EXTENDER_BUILTIN_X_RIGHT:
|
||||
width = Math.ceil(imageWidth * scale)
|
||||
break
|
||||
case EXTENDER_BUILTIN_Y_TOP:
|
||||
y = -Math.ceil(imageHeight * (scale - 1))
|
||||
case EXTENDER_Y:
|
||||
y = -Math.ceil((imageHeight * (scale - 1)) / 2)
|
||||
height = Math.ceil(imageHeight * scale)
|
||||
break
|
||||
case EXTENDER_BUILTIN_Y_BOTTOM:
|
||||
height = Math.ceil(imageHeight * scale)
|
||||
break
|
||||
case EXTENDER_BUILTIN_ALL:
|
||||
case EXTENDER_ALL:
|
||||
x = -Math.ceil((imageWidth * (scale - 1)) / 2)
|
||||
y = -Math.ceil((imageHeight * (scale - 1)) / 2)
|
||||
width = Math.ceil(imageWidth * scale)
|
||||
|
||||
Reference in New Issue
Block a user