add negative prompt
This commit is contained in:
parent
b7d504cba6
commit
8c2904c9c8
@ -9,6 +9,7 @@ export default async function inpaint(
|
|||||||
settings: Settings,
|
settings: Settings,
|
||||||
croperRect: Rect,
|
croperRect: Rect,
|
||||||
prompt?: string,
|
prompt?: string,
|
||||||
|
negativePrompt?: string,
|
||||||
sizeLimit?: string,
|
sizeLimit?: string,
|
||||||
seed?: number
|
seed?: number
|
||||||
) {
|
) {
|
||||||
@ -34,6 +35,10 @@ export default async function inpaint(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fd.append('prompt', prompt === undefined ? '' : prompt)
|
fd.append('prompt', prompt === undefined ? '' : prompt)
|
||||||
|
fd.append(
|
||||||
|
'negativePrompt',
|
||||||
|
negativePrompt === undefined ? '' : negativePrompt
|
||||||
|
)
|
||||||
fd.append('croperX', croperRect.x.toString())
|
fd.append('croperX', croperRect.x.toString())
|
||||||
fd.append('croperY', croperRect.y.toString())
|
fd.append('croperY', croperRect.y.toString())
|
||||||
fd.append('croperHeight', croperRect.height.toString())
|
fd.append('croperHeight', croperRect.height.toString())
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
fileState,
|
fileState,
|
||||||
isInpaintingState,
|
isInpaintingState,
|
||||||
isSDState,
|
isSDState,
|
||||||
|
negativePropmtState,
|
||||||
propmtState,
|
propmtState,
|
||||||
runManuallyState,
|
runManuallyState,
|
||||||
seedState,
|
seedState,
|
||||||
@ -88,6 +89,7 @@ function mouseXY(ev: SyntheticEvent) {
|
|||||||
export default function Editor() {
|
export default function Editor() {
|
||||||
const [file, setFile] = useRecoilState(fileState)
|
const [file, setFile] = useRecoilState(fileState)
|
||||||
const promptVal = useRecoilValue(propmtState)
|
const promptVal = useRecoilValue(propmtState)
|
||||||
|
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)
|
||||||
@ -261,6 +263,7 @@ export default function Editor() {
|
|||||||
settings,
|
settings,
|
||||||
croperRect,
|
croperRect,
|
||||||
prompt,
|
prompt,
|
||||||
|
negativePromptVal,
|
||||||
sizeLimit.toString(),
|
sizeLimit.toString(),
|
||||||
sdSeed
|
sdSeed
|
||||||
)
|
)
|
||||||
@ -311,6 +314,7 @@ export default function Editor() {
|
|||||||
croperRect,
|
croperRect,
|
||||||
sizeLimit,
|
sizeLimit,
|
||||||
promptVal,
|
promptVal,
|
||||||
|
negativePromptVal,
|
||||||
drawOnCurrentRender,
|
drawOnCurrentRender,
|
||||||
hadDrawSomething,
|
hadDrawSomething,
|
||||||
drawLinesOnMask,
|
drawLinesOnMask,
|
||||||
|
@ -25,6 +25,14 @@
|
|||||||
gap: 12rem;
|
gap: 12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.setting-block-content-v {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.setting-block-content-title {
|
.setting-block-content-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -8,13 +8,18 @@ interface SettingBlockProps {
|
|||||||
input: ReactNode
|
input: ReactNode
|
||||||
optionDesc?: ReactNode
|
optionDesc?: ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
|
layout?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingBlock(props: SettingBlockProps) {
|
function SettingBlock(props: SettingBlockProps) {
|
||||||
const { title, titleSuffix, desc, input, optionDesc, className } = props
|
const { title, titleSuffix, desc, input, optionDesc, className, layout } =
|
||||||
|
props
|
||||||
|
const contentClass =
|
||||||
|
layout === 'h' ? 'setting-block-content' : 'setting-block-content-v'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`setting-block ${className}`}>
|
<div className={`setting-block ${className}`}>
|
||||||
<div className="setting-block-content">
|
<div className={contentClass}>
|
||||||
<div className="setting-block-content-title">
|
<div className="setting-block-content-title">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
{desc ? (
|
{desc ? (
|
||||||
@ -34,4 +39,8 @@ function SettingBlock(props: SettingBlockProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingBlock.defaultProps = {
|
||||||
|
layout: 'h',
|
||||||
|
}
|
||||||
|
|
||||||
export default SettingBlock
|
export default SettingBlock
|
||||||
|
@ -55,3 +55,31 @@
|
|||||||
// // border-radius: 4px;
|
// // border-radius: 4px;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.negative-prompt {
|
||||||
|
all: unset;
|
||||||
|
border-width: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
min-height: 150px;
|
||||||
|
max-width: 200px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 0.8rem;
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
border-width: 0;
|
||||||
|
outline: 1px solid var(--yellow-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:-webkit-input-placeholder {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:-moz-input-placeholder {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:-ms-input-placeholder {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { FormEvent, useState } from 'react'
|
||||||
import { useRecoilState } from 'recoil'
|
import { useRecoilState } from 'recoil'
|
||||||
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||||
import { useToggle } from 'react-use'
|
import { useToggle } from 'react-use'
|
||||||
import { SDSampler, settingState } from '../../store/Atoms'
|
import { negativePropmtState, SDSampler, settingState } from '../../store/Atoms'
|
||||||
import NumberInputSetting from '../Settings/NumberInputSetting'
|
import NumberInputSetting from '../Settings/NumberInputSetting'
|
||||||
import SettingBlock from '../Settings/SettingBlock'
|
import SettingBlock from '../Settings/SettingBlock'
|
||||||
import Selector from '../shared/Selector'
|
import Selector from '../shared/Selector'
|
||||||
import { Switch, SwitchThumb } from '../shared/Switch'
|
import { Switch, SwitchThumb } from '../shared/Switch'
|
||||||
|
import TextAreaInput from '../shared/Textarea'
|
||||||
|
|
||||||
const INPUT_WIDTH = 30
|
const INPUT_WIDTH = 30
|
||||||
|
|
||||||
@ -14,6 +15,15 @@ const INPUT_WIDTH = 30
|
|||||||
const SidePanel = () => {
|
const SidePanel = () => {
|
||||||
const [open, toggleOpen] = useToggle(true)
|
const [open, toggleOpen] = useToggle(true)
|
||||||
const [setting, setSettingState] = useRecoilState(settingState)
|
const [setting, setSettingState] = useRecoilState(settingState)
|
||||||
|
const [negativePrompt, setNegativePrompt] =
|
||||||
|
useRecoilState(negativePropmtState)
|
||||||
|
|
||||||
|
const handleOnInput = (evt: FormEvent<HTMLTextAreaElement>) => {
|
||||||
|
evt.preventDefault()
|
||||||
|
evt.stopPropagation()
|
||||||
|
const target = evt.target as HTMLTextAreaElement
|
||||||
|
setNegativePrompt(target.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="side-panel">
|
<div className="side-panel">
|
||||||
@ -59,7 +69,7 @@ const SidePanel = () => {
|
|||||||
title="Steps"
|
title="Steps"
|
||||||
width={INPUT_WIDTH}
|
width={INPUT_WIDTH}
|
||||||
value={`${setting.sdSteps}`}
|
value={`${setting.sdSteps}`}
|
||||||
desc="Large steps result in better result, but more time-consuming"
|
desc="The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference."
|
||||||
onValue={value => {
|
onValue={value => {
|
||||||
const val = value.length === 0 ? 0 : parseInt(value, 10)
|
const val = value.length === 0 ? 0 : parseInt(value, 10)
|
||||||
setSettingState(old => {
|
setSettingState(old => {
|
||||||
@ -88,7 +98,7 @@ const SidePanel = () => {
|
|||||||
width={INPUT_WIDTH}
|
width={INPUT_WIDTH}
|
||||||
allowFloat
|
allowFloat
|
||||||
value={`${setting.sdGuidanceScale}`}
|
value={`${setting.sdGuidanceScale}`}
|
||||||
desc="TODO"
|
desc="Higher guidance scale encourages to generate images that are closely linked to the text prompt, usually at the expense of lower image quality."
|
||||||
onValue={value => {
|
onValue={value => {
|
||||||
const val = value.length === 0 ? 0 : parseFloat(value)
|
const val = value.length === 0 ? 0 : parseFloat(value)
|
||||||
setSettingState(old => {
|
setSettingState(old => {
|
||||||
@ -101,7 +111,7 @@ const SidePanel = () => {
|
|||||||
title="Mask Blur"
|
title="Mask Blur"
|
||||||
width={INPUT_WIDTH}
|
width={INPUT_WIDTH}
|
||||||
value={`${setting.sdMaskBlur}`}
|
value={`${setting.sdMaskBlur}`}
|
||||||
desc="TODO"
|
desc="Blur the edge of mask area. The higher the number the smoother blend with the original image"
|
||||||
onValue={value => {
|
onValue={value => {
|
||||||
const val = value.length === 0 ? 0 : parseInt(value, 10)
|
const val = value.length === 0 ? 0 : parseInt(value, 10)
|
||||||
setSettingState(old => {
|
setSettingState(old => {
|
||||||
@ -167,6 +177,20 @@ const SidePanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SettingBlock
|
||||||
|
className="sub-setting-block"
|
||||||
|
title="Negative prompt"
|
||||||
|
layout="v"
|
||||||
|
input={
|
||||||
|
<TextAreaInput
|
||||||
|
className="negative-prompt"
|
||||||
|
value={negativePrompt}
|
||||||
|
onInput={handleOnInput}
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</PopoverPrimitive.Content>
|
</PopoverPrimitive.Content>
|
||||||
</PopoverPrimitive.Portal>
|
</PopoverPrimitive.Portal>
|
||||||
</PopoverPrimitive.Root>
|
</PopoverPrimitive.Root>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { FocusEvent, InputHTMLAttributes, RefObject } from 'react'
|
import React, { FocusEvent, InputHTMLAttributes, RefObject } from 'react'
|
||||||
import { useClickAway } from 'react-use'
|
|
||||||
import { useRecoilState } from 'recoil'
|
import { useRecoilState } from 'recoil'
|
||||||
import { appState } from '../../store/Atoms'
|
import { appState } from '../../store/Atoms'
|
||||||
|
|
||||||
|
45
lama_cleaner/app/src/components/shared/Textarea.tsx
Normal file
45
lama_cleaner/app/src/components/shared/Textarea.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React, { FocusEvent, TextareaHTMLAttributes } from 'react'
|
||||||
|
import { useRecoilState } from 'recoil'
|
||||||
|
import { appState } from '../../store/Atoms'
|
||||||
|
|
||||||
|
const TextAreaInput = React.forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
TextareaHTMLAttributes<HTMLTextAreaElement>
|
||||||
|
>((props, ref) => {
|
||||||
|
const { onFocus, onBlur, ...itemProps } = props
|
||||||
|
const [_, setAppState] = useRecoilState(appState)
|
||||||
|
|
||||||
|
const handleOnFocus = (evt: FocusEvent<any>) => {
|
||||||
|
setAppState(old => {
|
||||||
|
return { ...old, disableShortCuts: true }
|
||||||
|
})
|
||||||
|
onFocus?.(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnBlur = (evt: FocusEvent<any>) => {
|
||||||
|
setAppState(old => {
|
||||||
|
return { ...old, disableShortCuts: false }
|
||||||
|
})
|
||||||
|
onBlur?.(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
{...itemProps}
|
||||||
|
ref={ref}
|
||||||
|
onFocus={handleOnFocus}
|
||||||
|
onBlur={handleOnBlur}
|
||||||
|
onPaste={evt => evt.stopPropagation()}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.currentTarget.blur()
|
||||||
|
}
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TextAreaInput
|
@ -43,6 +43,11 @@ export const propmtState = atom<string>({
|
|||||||
default: '',
|
default: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const negativePropmtState = atom<string>({
|
||||||
|
key: 'negativePromptState',
|
||||||
|
default: '',
|
||||||
|
})
|
||||||
|
|
||||||
export const isInpaintingState = selector({
|
export const isInpaintingState = selector({
|
||||||
key: 'isInpainting',
|
key: 'isInpainting',
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
|
@ -140,6 +140,7 @@ class SD(InpaintModel):
|
|||||||
|
|
||||||
output = self.model(
|
output = self.model(
|
||||||
prompt=config.prompt,
|
prompt=config.prompt,
|
||||||
|
negative_prompt=config.negative_prompt,
|
||||||
mask_image=PIL.Image.fromarray(mask[:, :, -1], mode="L"),
|
mask_image=PIL.Image.fromarray(mask[:, :, -1], mode="L"),
|
||||||
strength=config.sd_strength,
|
strength=config.sd_strength,
|
||||||
num_inference_steps=config.sd_steps,
|
num_inference_steps=config.sd_steps,
|
||||||
|
@ -30,6 +30,7 @@ class Config(BaseModel):
|
|||||||
hd_strategy_resize_limit: int
|
hd_strategy_resize_limit: int
|
||||||
|
|
||||||
prompt: str = ""
|
prompt: str = ""
|
||||||
|
negative_prompt: str = ""
|
||||||
# 始终是在原图尺度上的值
|
# 始终是在原图尺度上的值
|
||||||
use_croper: bool = False
|
use_croper: bool = False
|
||||||
croper_x: int = None
|
croper_x: int = None
|
||||||
|
@ -113,6 +113,7 @@ def process():
|
|||||||
hd_strategy_crop_trigger_size=form["hdStrategyCropTrigerSize"],
|
hd_strategy_crop_trigger_size=form["hdStrategyCropTrigerSize"],
|
||||||
hd_strategy_resize_limit=form["hdStrategyResizeLimit"],
|
hd_strategy_resize_limit=form["hdStrategyResizeLimit"],
|
||||||
prompt=form["prompt"],
|
prompt=form["prompt"],
|
||||||
|
negative_prompt=form["negativePrompt"],
|
||||||
use_croper=form["useCroper"],
|
use_croper=form["useCroper"],
|
||||||
croper_x=form["croperX"],
|
croper_x=form["croperX"],
|
||||||
croper_y=form["croperY"],
|
croper_y=form["croperY"],
|
||||||
|
@ -195,6 +195,44 @@ def test_runway_sd_1_5(sd_device, strategy, sampler, cpu_textencoder, disable_ns
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("sd_device", ['cuda'])
|
||||||
|
@pytest.mark.parametrize("strategy", [HDStrategy.ORIGINAL])
|
||||||
|
@pytest.mark.parametrize("sampler", [SDSampler.ddim])
|
||||||
|
def test_runway_sd_1_5_negative_prompt(sd_device, strategy, sampler):
|
||||||
|
def callback(i, t, latents):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if sd_device == 'cuda' and not torch.cuda.is_available():
|
||||||
|
return
|
||||||
|
|
||||||
|
sd_steps = 50
|
||||||
|
model = ModelManager(name="sd1.5",
|
||||||
|
device=sd_device,
|
||||||
|
hf_access_token="",
|
||||||
|
sd_run_local=True,
|
||||||
|
sd_disable_nsfw=True,
|
||||||
|
sd_cpu_textencoder=True,
|
||||||
|
callback=callback)
|
||||||
|
cfg = get_config(
|
||||||
|
strategy,
|
||||||
|
sd_steps=sd_steps,
|
||||||
|
prompt='Face of a fox, high resolution, sitting on a park bench',
|
||||||
|
negative_prompt='orange, yellow, small',
|
||||||
|
sd_sampler=sampler
|
||||||
|
)
|
||||||
|
|
||||||
|
name = f"{sampler}_negative_prompt"
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
model,
|
||||||
|
cfg,
|
||||||
|
f"runway_sd_{strategy.capitalize()}_{name}.png",
|
||||||
|
img_p=current_dir / "overture-creations-5sI6fQgYIuo.png",
|
||||||
|
mask_p=current_dir / "overture-creations-5sI6fQgYIuo_mask.png",
|
||||||
|
fx=1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"strategy", [HDStrategy.ORIGINAL, HDStrategy.RESIZE, HDStrategy.CROP]
|
"strategy", [HDStrategy.ORIGINAL, HDStrategy.RESIZE, HDStrategy.CROP]
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user