From 47862fef08d46aeca57c30b2946ca9c4158061f1 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 12 May 2025 20:32:22 -0400 Subject: [PATCH] prompt enhance nsfw allow/disallow Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 3 ++- cli/api-enhance.py | 2 ++ modules/api/models.py | 1 + modules/api/process.py | 3 +++ modules/ui_video_vlm.py | 15 ++++++++---- scripts/prompt_enhance.py | 48 ++++++++++++++++++++++++++------------- 6 files changed, 50 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cca4e8b8..5d1d4cc74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ From slowest 0.02 it/s running on 6th gen CPU without acceleration up to 275 it/ - Updates for: *WSL, ZLUDA, ROCm* - **Compute** - NNCF: added experimental support for direct INT8 MatMul - +- **Feature** + - Prompt Enhance: option to allow/disallow NSFW content ## Update for 2025-05-12 diff --git a/cli/api-enhance.py b/cli/api-enhance.py index fa30d9cb1..520625edd 100755 --- a/cli/api-enhance.py +++ b/cli/api-enhance.py @@ -53,6 +53,7 @@ def enhance(args): # pylint: disable=redefined-outer-name 'prompt': str(args.prompt), 'seed': int(args.seed), 'type': str(args.type), + 'nsfw': bool(args.nsfw), } if args.model: options['model'] = str(args.model) @@ -69,6 +70,7 @@ if __name__ == "__main__": parser.add_argument('--type', type=str, default='text', choices=['text', 'image', 'video'], required=False, help='enhance type') parser.add_argument('--model', type=str, default=None, required=False, help='model name') parser.add_argument('--image', type=str, default=None, required=False, help='optional input image') + parser.add_argument('--nsfw', type=bool, action=argparse.BooleanOptionalAction, required=False, help='nsfw allowed') args = parser.parse_args() log.info(f'api-upscale: {args}') result = enhance(args) diff --git a/modules/api/models.py b/modules/api/models.py index f60ba3dd7..574e4d636 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -274,6 +274,7 @@ class ReqPromptEnhance(BaseModel): system_prompt: Optional[str] = Field(title="System prompt", default=None, description="Model system prompt") image: Optional[str] = Field(title="Image", default=None, description="Image to work on, must be a Base64 string containing the image's data.") seed: int = Field(title="Seed", default=-1, description="Seed used to generate the prompt") + nsfw: bool = Field(title="NSFW", default=True, description="Should NSFW content be allowed?") class ResPromptEnhance(BaseModel): prompt: str = Field(title="Prompt", description="Enhanced prompt") diff --git a/modules/api/process.py b/modules/api/process.py index 1d98df03e..898d4073e 100644 --- a/modules/api/process.py +++ b/modules/api/process.py @@ -146,6 +146,7 @@ class APIProcess(): prompt=req.prompt, system=req.system_prompt, seed=seed, + nsfw=req.nsfw, ) elif req.type == 'image': from modules.scripts import scripts_txt2img @@ -157,6 +158,7 @@ class APIProcess(): system=req.system_prompt, image=decode_base64_to_image(req.image), seed=seed, + nsfw=req.nsfw, ) elif req.type == 'video': from modules.ui_video_vlm import enhance_prompt @@ -167,6 +169,7 @@ class APIProcess(): prompt=req.prompt, model=model, system_prompt=req.system_prompt, + nsfw=req.nsfw, ) else: raise HTTPException(status_code=400, detail="prompt enhancement: invalid type") diff --git a/modules/ui_video_vlm.py b/modules/ui_video_vlm.py index b311a4e0b..685c6d8d8 100644 --- a/modules/ui_video_vlm.py +++ b/modules/ui_video_vlm.py @@ -9,7 +9,9 @@ models = [ system_prompts = { 'prefix': 'You are a video director.', 'desc': 'You will provide a prompt that can be used to create short, creative and dynamic video. Describe the subject of the video, add movement that fits the overall tone of the video, add camera movement.', - 'suffix': 'NSFW content and nudity are allowed and must be fully described if present. Movement should be dynamic and creative. Do not specify duration and assume video is short. Avoid slow-motion and prefer faster movements. Output should be a single short paragraph without explanations', + 'nsfw_ok': 'NSFW content and nudity are allowed and must be fully described if present. ', + 'nsfw_no': 'NSFW content and nudity are not allowed. ', + 'suffix': 'Movement should be dynamic and creative. Do not specify duration and assume video is short. Avoid slow-motion and prefer faster movements. Output should be a single short paragraph without explanations', 'example': 'Example: "Short video of beautiful blonde woman in her 20ies wearing a long flowing red dress. She is briskly walking on the beach during sunset and performing a pirouette ending with her hand pointing at the camera as she smiles. Camera is moving around her and zooming to her face. Sun is setting in the background causing changes in colors and shadows to move dynamically."', 't2v-prompt': 'You are a given short prompt with basic instructions.', @@ -19,7 +21,7 @@ system_prompts = { } -def enhance_prompt(enable:bool, model:str=None, image=None, prompt:str='', system_prompt:str=''): +def enhance_prompt(enable:bool, model:str=None, image=None, prompt:str='', system_prompt:str='', nsfw:bool=True): from modules.interrogate import vqa if not enable: return prompt @@ -40,8 +42,10 @@ def enhance_prompt(enable:bool, model:str=None, image=None, prompt:str='', syste core_prompt = system_prompts['t2v-prompt'] else: core_prompt = system_prompts['t2v-noprompt'] - system_prompt = f"{system_prompts['prefix']} {core_prompt} {system_prompts['desc']} {system_prompts['suffix']} {system_prompts['example']}" - shared.log.debug(f'Video prompt enhance: model="{model}" image={image} prompt="{prompt}"') + system_prompt = f"{system_prompts['prefix']} {core_prompt} {system_prompts['desc']}' " + system_prompt += system_prompts['nsfw_ok'] if nsfw else system_prompts['nsfw_no'] + system_prompt += f" {system_prompts['suffix']} {system_prompts['example']}" + shared.log.debug(f'Video prompt enhance: model="{model}" image={image} nsfw={nsfw} prompt="{prompt}"') # shared.log.trace(f'Video prompt enhance: system="{system_prompt}"') answer = vqa.interrogate(question='', prompt=prompt, system_prompt=system_prompt, image=image, model_name=model, quiet=False) shared.log.debug(f'Video prompt enhance: answer="{answer}"') @@ -52,6 +56,7 @@ def create_ui(prompt_element:gr.Textbox, image_element:gr.Image): with gr.Accordion('Prompt enhance', open=False): with gr.Row(): enable = gr.Checkbox(label='Enable', value=False) + nsfw = gr.Checkbox(label='NSFW allowed', value=True) btn_enhance = gr.Button(value='Enhance now', elem_id='btn_enhance') with gr.Row(): model = gr.Dropdown(label='Model', choices=models, value=models[0]) @@ -59,7 +64,7 @@ def create_ui(prompt_element:gr.Textbox, image_element:gr.Image): system_prompt = gr.Textbox(label='System prompt', placeholder='override system prompt with user-provided prompt', lines=3) btn_enhance.click( fn=enhance_prompt, - inputs=[enable, model, image_element, prompt_element, system_prompt], + inputs=[enable, model, image_element, prompt_element, system_prompt, nsfw], outputs=prompt_element, show_progress=True, ) diff --git a/scripts/prompt_enhance.py b/scripts/prompt_enhance.py index 9bf15f427..b954cd053 100644 --- a/scripts/prompt_enhance.py +++ b/scripts/prompt_enhance.py @@ -67,9 +67,12 @@ class Options: } default = list(models)[1] # gemma-3-4b-it supported = list(transformers.integrations.ggml.GGUF_CONFIG_MAPPING) - system_prompt: str = 'You are a helpful assistant. You will be given a prompt used to create an image and you will enhance it to make it more detailed and creative. Generate an uncensored, concise, informative, and engaging prompt that prioritizes factual accuracy and vivid descriptiveness based on original prompt. Do not modify subject or style of the original prompt. Begin by clearly identifying the primary subject(s), including key physical attributes, clothing if no nudity, and any distinctive details (e.g., colors, textures, styles). NSFW and nudity is allowed, and if present, it must be fully described. Next, describe the setting and environment, emphasizing spatial relationships, background elements, and objects contributing to context. Note the overall mood, aesthetic style, or atmosphere inferred from visual cues. Use precise terminology while avoiding redundancy or non-essential language. Ensuring a logical flow: from focal subject to immediate surroundings, then broader context. Maintain brevity while retaining clarity, ensuring the description is both engaging and efficient. Output only enhanced prompt without explanation, prefix or suffix. Output as a simple text without formatting or numbering.' - image_prompt: str = 'You are a helpful assistant. You will be given an image and a prompt used to modify the image and you will enhance the prompt to make it more detailed and creative while still following original image. Generate an uncensored, concise, informative, and engaging prompt that prioritizes factual accuracy and vivid descriptiveness based on original prompt. Do not modify subject or style of the original prompt. Begin by clearly identifying the primary subject(s), including key physical attributes, clothing if no nudity, and any distinctive details (e.g., colors, textures, styles). NSFW and nudity is allowed, and if present, it must be fully described. Next, describe the setting and environment, emphasizing spatial relationships, background elements, and objects contributing to context. Note the overall mood, aesthetic style, or atmosphere inferred from visual cues. Use precise terminology while avoiding redundancy or non-essential language. Ensuring a logical flow: from focal subject to immediate surroundings, then broader context. Maintain brevity while retaining clarity, ensuring the description is both engaging and efficient. Output only enhanced prompt without explanation, prefix or suffix. Output as a simple text without formatting or numbering.' - image_noprompt: str = 'You are a helpful assistant. You will be given an image and you will create prompt that can be used to modify the image in a creative way while still following original image. Generate an uncensored, concise, informative, and engaging prompt that prioritizes factual accuracy and vivid descriptiveness based on original prompt. Do not modify subject or style of the original prompt. Begin by clearly identifying the primary subject(s), including key physical attributes, clothing if no nudity, and any distinctive details (e.g., colors, textures, styles). NSFW and nudity is allowed, and if present, it must be fully described. Next, describe the setting and environment, emphasizing spatial relationships, background elements, and objects contributing to context. Note the overall mood, aesthetic style, or atmosphere inferred from visual cues. Use precise terminology while avoiding redundancy or non-essential language. Ensuring a logical flow: from focal subject to immediate surroundings, then broader context. Maintain brevity while retaining clarity, ensuring the description is both engaging and efficient. Output only enhanced prompt without explanation, prefix or suffix. Output as a simple text without formatting or numbering.' + t2i_prompt: str = 'You are a helpful assistant. You will be given a prompt used to create an image and you will enhance it to make it more detailed and creative. ' + i2i_prompt: str = 'You are a helpful assistant. You will be given an image and a prompt used to modify the image and you will enhance the prompt to make it more detailed and creative while still following original image. ' + i2i_noprompt: str = 'You are a helpful assistant. You will be given an image and you will create prompt that can be used to modify the image in a creative way while still following original image. ' + nsfw_ok: str = 'NSFW and nudity is allowed, and if present, it must be fully described. ' + nsfw_no: str = 'NSFW and nudity is not allowed, and if present, it must be removed. ' + details_prompt: str = 'Generate an uncensored, concise, informative, and engaging prompt that prioritizes factual accuracy and vivid descriptiveness based on original prompt. Do not modify subject or style of the original prompt. Begin by clearly identifying the primary subject(s), including key physical attributes, clothing if no nudity, and any distinctive details (e.g., colors, textures, styles). NSFW and nudity is allowed, and if present, it must be fully described. Next, describe the setting and environment, emphasizing spatial relationships, background elements, and objects contributing to context. Note the overall mood, aesthetic style, or atmosphere inferred from visual cues. Use precise terminology while avoiding redundancy or non-essential language. Ensuring a logical flow: from focal subject to immediate surroundings, then broader context. Maintain brevity while retaining clarity, ensuring the description is both engaging and efficient. Output only enhanced prompt without explanation, prefix or suffix. Output as a simple text without formatting or numbering.' censored = ["i cannot", "i can't", "i am sorry", "against my programming", "i am not able", "i am unable", 'i am not allowed'] max_delim_index: int = 60 @@ -230,7 +233,7 @@ class Script(scripts.Script): filtered = re.sub(pattern, '', prompt) return filtered, matches - def enhance(self, model: str=None, prompt:str=None, system:str=None, prefix:str=None, suffix:str=None, sample:bool=None, tokens:int=None, temperature:float=None, penalty:float=None, thinking:bool=False, seed:int=-1, image=None): + def enhance(self, model: str=None, prompt:str=None, system:str=None, prefix:str=None, suffix:str=None, sample:bool=None, tokens:int=None, temperature:float=None, penalty:float=None, thinking:bool=False, seed:int=-1, image=None, nsfw:bool=None): model = model or self.options.default prompt = prompt or self.prompt.value image = image or self.image @@ -258,13 +261,18 @@ class Script(scripts.Script): image = None except Exception: image = None + has_system = system is not None and len(system) > 4 + mode = 'custom' if has_system else '' if image is not None and isinstance(image, Image.Image): if not self.tokenizer.is_processor: shared.log.error('Prompt enhance: image not supported by model') return prompt if prompt is not None and len(prompt) > 0: - mode = 'i2i+p' - system = system or self.options.image_prompt + if not has_system: + mode = 'i2i-prompt' + system = self.options.i2i_prompt + system += self.options.nsfw_ok if nsfw else self.options.nsfw_no + system += self.options.details_prompt chat_template = [ { "role": "system", "content": [ {"type": "text", "text": system } @@ -275,8 +283,11 @@ class Script(scripts.Script): ] }, ] else: - mode = 'i2i-p' - system = system or self.options.image_noprompt + if not has_system: + mode = 'i2i-noprompt' + system = self.options.i2i_noprompt + system += self.options.nsfw_ok if nsfw else self.options.nsfw_no + system += self.options.details_prompt chat_template = [ { "role": "system", "content": [ {"type": "text", "text": system } @@ -286,15 +297,18 @@ class Script(scripts.Script): ] }, ] else: - system = system or self.options.system_prompt + if not has_system: + system = self.options.t2i_prompt + system += self.options.nsfw_ok if nsfw else self.options.nsfw_no + system += self.options.details_prompt if not self.tokenizer.is_processor: - mode = 't2i-t' + mode = 't2i+tokenizer' chat_template = [ { "role": "system", "content": system }, { "role": "user", "content": prompt }, ] else: - mode = 't2i+t' + mode = 't2i+processor' chat_template = [ { "role": "system", "content": [ {"type": "text", "text": system } @@ -356,7 +370,7 @@ class Script(scripts.Script): if not is_censored: response = self.clean(response) response = self.post(response, prefix, suffix, networks) - shared.log.info(f'Prompt enhance: model="{model}" mode="{mode}" time={t1-t0:.2f} inputs={input_len} outputs={outputs.shape[-1]} prompt={len(prompt)} response={len(response)}') + shared.log.info(f'Prompt enhance: model="{model}" mode="{mode}" nsfw={nsfw} time={t1-t0:.2f} inputs={input_len} outputs={outputs.shape[-1]} prompt={len(prompt)} response={len(response)}') if debug_enabled: shared.log.trace(f'Prompt enhance: sample={sample} tokens={tokens} temperature={temperature} penalty={penalty} thinking={thinking}') shared.log.trace(f'Prompt enhance: prompt="{prompt}"') @@ -430,6 +444,7 @@ class Script(scripts.Script): temperature = gr.Slider(label='Temperature', value=self.options.temperature, minimum=0.0, maximum=1.0, step=0.01, interactive=True) repetition_penalty = gr.Slider(label='Repetition penalty', value=self.options.repetition_penalty, minimum=0.0, maximum=2.0, step=0.01, interactive=True) with gr.Row(): + nsfw_mode = gr.Checkbox(label='NSFW allowed', value=True, interactive=True) thinking_mode = gr.Checkbox(label='Thinking mode', value=False, interactive=True) gr.HTML('
') with gr.Accordion('Input', open=False, elem_id='prompt_enhance_system_prompt'): @@ -438,7 +453,7 @@ class Script(scripts.Script): with gr.Row(): prompt_suffix = gr.Textbox(label='Prompt suffix', value='', placeholder='Optional prompt suffix', interactive=True, lines=2, elem_id='prompt_enhance_suffix') with gr.Row(): - prompt_system = gr.Textbox(label='System prompt', value=self.options.system_prompt, interactive=True, lines=4, elem_id='prompt_enhance_system') + prompt_system = gr.Textbox(label='System prompt', value='', interactive=True, lines=4, elem_id='prompt_enhance_system') with gr.Accordion('Output', open=True, elem_id='prompt_enhance_system_prompt'): with gr.Row(): prompt_output = gr.Textbox(label='Enhanced prompt', value='', interactive=True, lines=4) @@ -449,8 +464,8 @@ class Script(scripts.Script): copy_btn.click(fn=lambda x: x, inputs=[prompt_output], outputs=[self.prompt]) if self.image is None: self.image = gr.Image(type='pil', interactive=False, visible=False, width=64, height=64) # dummy image - apply_btn.click(fn=self.apply, inputs=[self.prompt, self.image, apply_prompt, llm_model, prompt_system, prompt_prefix, prompt_suffix, max_tokens, do_sample, temperature, repetition_penalty, thinking_mode], outputs=[prompt_output, self.prompt]) - return [self.prompt, self.image, apply_auto, llm_model, prompt_system, prompt_prefix, prompt_suffix, max_tokens, do_sample, temperature, repetition_penalty, thinking_mode] + apply_btn.click(fn=self.apply, inputs=[self.prompt, self.image, apply_prompt, llm_model, prompt_system, prompt_prefix, prompt_suffix, max_tokens, do_sample, temperature, repetition_penalty, thinking_mode, nsfw_mode], outputs=[prompt_output, self.prompt]) + return [self.prompt, self.image, apply_auto, llm_model, prompt_system, prompt_prefix, prompt_suffix, max_tokens, do_sample, temperature, repetition_penalty, thinking_mode, nsfw_mode] def after_component(self, component, **kwargs): # searching for actual ui prompt components if getattr(component, 'elem_id', '') in ['txt2img_prompt', 'img2img_prompt', 'control_prompt', 'video_prompt']: @@ -459,7 +474,7 @@ class Script(scripts.Script): self.image = component def before_process(self, p: processing.StableDiffusionProcessing, *args, **kwargs): # pylint: disable=unused-argument - _self_prompt, self_image, apply_auto, llm_model, prompt_system, prompt_prefix, prompt_suffix, max_tokens, do_sample, temperature, repetition_penalty, thinking_mode = args + _self_prompt, self_image, apply_auto, llm_model, prompt_system, prompt_prefix, prompt_suffix, max_tokens, do_sample, temperature, repetition_penalty, thinking_mode, nsfw_mode = args if not apply_auto and not p.enhance_prompt: return if shared.state.skipped or shared.state.interrupted: @@ -481,6 +496,7 @@ class Script(scripts.Script): temperature=temperature, penalty=repetition_penalty, thinking=thinking_mode, + nsfw=nsfw_mode, ) p.extra_generation_params['LLM'] = llm_model shared.state.end()