Files
SETI 319be75ed0 fix(webui): add Forge Neo prompt parser fallback (#125)
Forge Neo moved parse_prompt_attention from modules.prompt_parser to
backend.text_processing.parsing, causing dtg.py and tipo.py to fail during
script loading.

Keep the existing import as the primary path and fall back only when it raises
ImportError. This preserves the current path for Original Forge and reForge
while restoring script loading on Forge Neo 2.29.1.

Verified GGUF prompt generation and image generation on Forge Neo after the
fallback was applied.
2026-10-03 14:57:22 +08:00

942 lines
36 KiB
Python

import ast
import json
import os
import pathlib
import random
import re
import sys
import gradio as gr
import torch
from modules import devices, infotext_utils, options, scripts, shared, ui
from modules.extra_networks import parse_prompt
from modules.processing import (
StableDiffusionProcessingImg2Img,
StableDiffusionProcessingTxt2Img,
fix_seed,
)
try:
from modules.prompt_parser import parse_prompt_attention
except ImportError:
from backend.text_processing.parsing import parse_prompt_attention
from modules.scripts import OnComponent, basedir
from modules.shared import opts
from modules.ui_components import ToolButton
# Try to import InputAccordion (WebUI >= 1.7)
try:
from modules.ui_components import InputAccordion
except ImportError:
InputAccordion = None
from kgen import models
from kgen.executor import tipo
from kgen.executor.tipo import (
parse_tipo_request,
tipo_runner,
)
from kgen.formatter import apply_format, seperate_tags
from kgen.logging import logger
from kgen.metainfo import TIPO_DEFAULT_FORMAT
ext_dir = basedir()
models.model_dir = pathlib.Path(ext_dir) / "models"
if ext_dir not in sys.path:
sys.path.insert(0, ext_dir)
from tipo_installer import ensure_runtime
SEED_MAX = 2**31 - 1
QUOTESWAP = str.maketrans("'\"", "\"'")
TOTAL_TAG_LENGTH = {
"VERY_SHORT": "very short",
"SHORT": "short",
"LONG": "long",
"VERY_LONG": "very long",
}
PROCESSING_TIMING = {
"BEFORE": "Before applying other prompt processings",
"AFTER": "After applying other prompt processings",
}
EXTRA_INPUT_LAYOUT = ["Side by side", "Stacked"]
DEFAULT_FORMAT = """<|special|>,
<|characters|>, <|copyrights|>,
<|artist|>,
<|general|>,
<|quality|>, <|meta|>, <|rating|>"""
TIMING_INFO_TEMPLATE = (
"_Prompt upsampling will be applied to {} "
"sd-dynamic-promps and the webui's styles feature are applied_"
)
INFOTEXT_KEY = "TIPO Parameters"
INFOTEXT_KEY_PROMPT = "TIPO prompt"
INFOTEXT_NL_PROMPT = "TIPO nl prompt"
INFOTEXT_KEY_FORMAT = "TIPO format"
PROMPT_INDICATE_HTML = """
<div style="height: 100%; width: 100%; display: flex; justify-content: center; align-items: center">
<span>
Original Prompt Loaded.<br>
Click "Apply" to apply the original prompt.
</span>
</div>
"""
RECOMMEND_MARKDOWN = """
### Recommended Model and Settings:
"""
MODEL_NAME_LIST = [
f"{model_name} | {file}"
for model_name, ggufs in models.tipo_model_list
for file in ggufs
] + [i[0] for i in models.tipo_model_list]
def on_process_timing_dropdown_changed(timing: str):
info = ""
if timing == PROCESSING_TIMING["BEFORE"]:
info = "**only the first image in batch**, **before**"
elif timing == PROCESSING_TIMING["AFTER"]:
info = "**all images in batch**, **after**"
else:
raise ValueError(f"Unknown timing: {timing}")
return TIMING_INFO_TEMPLATE.format(info)
def apply_strength(tag_map, strength_map, strength_map_nl, break_map):
for cate in tag_map:
new_list = []
# Skip natural language output at first
if isinstance(tag_map[cate], str):
# Ensure all the parts in the strength_map are in the prompt
if all(part in tag_map[cate] for part, strength in strength_map_nl):
org_prompt = tag_map[cate]
new_prompt = ""
for part, strength in strength_map_nl:
before, org_prompt = org_prompt.split(part, 1)
new_prompt += before.replace("(", "\\(").replace(")", "\\)")
part = part.replace("(", "\\(").replace(")", "\\)")
new_prompt += f"({part}:{strength})"
new_prompt += org_prompt
tag_map[cate] = new_prompt
continue
for org_tag in tag_map[cate]:
tag = org_tag.replace("(", "\\(").replace(")", "\\)")
if org_tag in strength_map:
new_list.append(f"({tag}:{strength_map[org_tag]})")
else:
new_list.append(tag)
if tag in break_map or org_tag in break_map:
new_list.append("BREAK")
tag_map[cate] = new_list
return tag_map
COMMENT_LINE = re.compile(r"^[^\S\n]*#[^\n]*(?:\n|$)", re.MULTILINE)
DANGLING = re.compile(r"(?:^[\s,]+|[\s,]+$)")
REPEATED_COMMA = re.compile(r",\s*(?=,)")
def strip_comments(text: str) -> str:
"""Drop lines whose first non-space character is '#' (issue #22)."""
if not text or "#" not in text:
return text
cleaned = REPEATED_COMMA.sub("", COMMENT_LINE.sub("", text))
return DANGLING.sub("", cleaned)
def embedding_names() -> set:
try:
from modules.sd_hijack import model_hijack
return {str(name).lower() for name in model_hijack.embedding_db.word_embeddings}
except Exception:
return set()
def embedding_spellings(tags) -> dict:
"""Map kgen's spaced form back to the original, for embeddings only.
seperate_tags spaces out underscores for any token no tag list claims. That
is correct for general tags, but an embedding name looks exactly like a tag,
so ask the webui which names are loaded and protect only those (issue #119).
"""
known = embedding_names()
if not known:
return {}
return {
tag.replace("_", " "): tag
for tag in tags
if len(tag) >= 4 and "_" in tag and tag.lower() in known
}
def restore_embeddings(tag_map, mapping):
if not mapping:
return tag_map
for cate, value in tag_map.items():
if isinstance(value, list):
tag_map[cate] = [mapping.get(tag, tag) for tag in value]
elif isinstance(value, str):
for spaced, original in mapping.items():
value = value.replace(spaced, original)
tag_map[cate] = value
return tag_map
class TIPOScript(scripts.Script):
def __init__(self):
super().__init__()
self.prompt_area = [None, None, None, None]
self.tag_prompt_area = [None, None]
self.prompt_area_row = [None, None]
self.tipo_accordion_row = [None, None]
self.current_model = None
self.generation_info = [None, None] # For txt2img and img2img
self.seed_reuse_btn = [None, None] # Store reuse buttons
self.seed_num_input = [None, None] # Store seed inputs
self.on_after_component_elem_id = [
("txt2img_prompt_row", lambda x: self.create_new_prompt_area(0, x)),
("txt2img_prompt", lambda x: self.set_prompt_area(0, x)),
("img2img_prompt_row", lambda x: self.create_new_prompt_area(1, x)),
("img2img_prompt", lambda x: self.set_prompt_area(1, x)),
("generation_info_txt2img", lambda x: self.set_generation_info(0, x)),
("generation_info_img2img", lambda x: self.set_generation_info(1, x)),
]
def create_new_prompt_area(self, i2i: int, prompt_row: OnComponent):
stacked = getattr(opts, "tipo_extra_input_layout", EXTRA_INPUT_LAYOUT[0])
lines = 2 if stacked == EXTRA_INPUT_LAYOUT[1] else 3
tag_kwargs = {
"label": "Tag Prompt",
"lines": lines,
"placeholder": "Tag Prompt for TIPO (Put Tags to Prompt region)",
}
nl_kwargs = {
"label": "Natural Language Prompt",
"lines": lines,
"placeholder": "Natural Language Prompt for TIPO (Put Tags to Prompt region)",
}
with gr.Row(visible=not opts.tipo_no_extra_input):
if stacked == EXTRA_INPUT_LAYOUT[1]:
# One full-width column keeps the main prompt at full width
# instead of splitting the row in half (issue #47).
with gr.Column(scale=1):
new_tag_prompt_area = gr.Textbox(**tag_kwargs)
new_prompt_area = gr.Textbox(**nl_kwargs)
else:
with gr.Column(scale=1):
new_tag_prompt_area = gr.Textbox(**tag_kwargs)
with gr.Column(scale=1):
new_prompt_area = gr.Textbox(**nl_kwargs)
# Create second row: Generate Prompt button (below the two input areas)
self.prompt_area_row[i2i] = gr.Row()
# Create third row: TIPO accordion
self.tipo_accordion_row[i2i] = gr.Row()
self.tag_prompt_area[i2i] = new_tag_prompt_area
self.prompt_area[i2i * 2 + 1] = new_prompt_area
def set_prompt_area(self, i2i: int, component: OnComponent):
self.prompt_area[i2i * 2] = component.component
def set_generation_info(self, i2i: int, component: OnComponent):
"""Store generation_info component reference and connect reuse seed button"""
self.generation_info[i2i] = component.component
# Connect reuse seed button if both components are available
if (
self.seed_reuse_btn[i2i] is not None
and self.seed_num_input[i2i] is not None
):
self.connect_reuse_seed_button(i2i)
def connect_reuse_seed_button(self, i2i: int):
"""Connect the reuse seed button to retrieve TIPO seed from generation info"""
if self.generation_info[i2i] is None:
return
self.seed_reuse_btn[i2i].click(
fn=self.get_tipo_seed_from_generation_info,
_js="(x, y) => [x, selected_gallery_index()]",
show_progress=False,
inputs=[self.generation_info[i2i]],
outputs=[self.seed_num_input[i2i]],
)
def title(self):
return "TIPO"
def show(self, _):
return scripts.AlwaysVisible
def ui(self, is_img2img):
with self.prompt_area_row[is_img2img]:
with gr.Column(
scale=1, min_width=180, visible=not opts.tipo_no_extra_input
):
prompt_gen = gr.Button(value="Generate Prompt")
with gr.Column(scale=6):
# Create accordion with enable checkbox on the label
tab_name = "img2img" if is_img2img else "txt2img"
if InputAccordion is not None:
# WebUI >= 1.7: Use InputAccordion (checkbox on accordion label)
tipo_acc = InputAccordion(
value=False,
label=self.title(),
elem_id=f"{tab_name}_tipo_accordion",
visible=True,
)
enabled_check = tipo_acc
else:
# WebUI < 1.7: Use regular Accordion
tipo_acc = gr.Accordion(
open=False,
label=self.title(),
elem_id=f"{tab_name}_tipo_accordion",
)
enabled_check = None # Will be created inside
with tipo_acc:
# For older WebUI versions, add enable checkbox inside
if InputAccordion is None:
enabled_check = gr.Checkbox(
label="Enabled",
value=False,
elem_id=f"{tab_name}_tipo_enabled",
)
with gr.Row():
with gr.Column():
with gr.Row():
with gr.Column(scale=1):
read_orig_prompt_btn = gr.Button(
size="sm",
value="Apply original prompt",
visible=False,
min_width=20,
)
with gr.Column(scale=3):
orig_prompt_area = gr.TextArea(visible=False)
orig_prompt_light = gr.HTML("")
orig_prompt_area.change(
lambda x: PROMPT_INDICATE_HTML * bool(x),
inputs=orig_prompt_area,
outputs=orig_prompt_light,
)
orig_prompt_area.change(
lambda x: gr.update(visible=bool(x)),
inputs=orig_prompt_area,
outputs=read_orig_prompt_btn,
)
read_orig_prompt_btn.click(
fn=lambda x: x,
inputs=[orig_prompt_area],
outputs=self.prompt_area[is_img2img],
)
tag_length_radio = gr.Radio(
label="Tags Length target",
choices=list(TOTAL_TAG_LENGTH.values()),
value=TOTAL_TAG_LENGTH["LONG"],
)
nl_length_radio = gr.Radio(
label="NL Length target",
choices=list(TOTAL_TAG_LENGTH.values()),
value=TOTAL_TAG_LENGTH["LONG"],
)
ban_tags_textbox = gr.Textbox(
label="Ban tags",
info="Separate with comma. Regex supported.",
value="",
placeholder="umbrella, official.*, .*text, ...",
)
format_dropdown = gr.Dropdown(
label="Prompt Format",
info="The format you want to apply to final prompt",
choices=list(TIPO_DEFAULT_FORMAT.keys()) + ["custom"],
value="Both, tag first (recommend)",
)
format_textarea = gr.TextArea(
value=TIPO_DEFAULT_FORMAT[
"Both, tag first (recommend)"
],
label="Custom Prompt Format",
visible=False,
placeholder="<|extended|>. <|general|>",
)
format_dropdown.change(
lambda x: gr.update(
visible=x == "custom",
value=TIPO_DEFAULT_FORMAT.get(
x, next(iter(TIPO_DEFAULT_FORMAT.values()))
),
),
inputs=format_dropdown,
outputs=format_textarea,
)
with gr.Group():
with gr.Row(elem_classes=["form"]):
seed_num_input = gr.Number(
label="Seed for upsampling tags",
minimum=-1,
step=1,
scale=4,
value=-1,
)
seed_random_btn = ToolButton(value=ui.random_symbol)
seed_reuse_btn = ToolButton(value=ui.reuse_symbol)
seed_shuffle_btn = gr.Button(
value="Shuffle", size="sm", scale=0
)
# Store references for later connection
self.seed_num_input[is_img2img] = seed_num_input
self.seed_reuse_btn[is_img2img] = seed_reuse_btn
seed_random_btn.click(
lambda: -1, outputs=[seed_num_input]
)
# Reuse button will be connected in set_generation_info
# when generation_info component becomes available
seed_shuffle_btn.click(
lambda: random.randint(0, 2**31 - 1),
outputs=[seed_num_input],
)
# Try to connect now if generation_info is already available
if self.generation_info[is_img2img] is not None:
self.connect_reuse_seed_button(is_img2img)
use_generation_seed = gr.Checkbox(
label="Follow the image generation seed",
info=(
"Upsample with the seed of the image being "
"generated, so the prompt tracks the image "
"instead of needing its own seed."
),
value=False,
)
with gr.Group():
process_timing_dropdown = gr.Dropdown(
label="Upsampling timing",
choices=list(PROCESSING_TIMING.values()),
value=PROCESSING_TIMING["AFTER"],
)
process_timing_md = gr.Markdown(
on_process_timing_dropdown_changed(
process_timing_dropdown.value
)
)
process_timing_dropdown.change(
on_process_timing_dropdown_changed,
inputs=[process_timing_dropdown],
outputs=[process_timing_md],
)
with gr.Column():
gr.Markdown(RECOMMEND_MARKDOWN)
model_dropdown = gr.Dropdown(
label="Model",
choices=MODEL_NAME_LIST,
value=MODEL_NAME_LIST[0],
)
gguf_use_cpu = gr.Checkbox(label="Use CPU (GGUF)")
no_formatting = gr.Checkbox(
label="No formatting", value=False
)
temperature_slider = gr.Slider(
label="Temperature",
info="← less random | more random →",
maximum=1.5,
minimum=0.1,
step=0.05,
value=0.5,
)
top_p_slider = gr.Slider(
label="Top-p",
info="← less unconfident tokens | more unconfident tokens →",
maximum=1,
minimum=0,
step=0.05,
value=0.95,
)
top_k_slider = gr.Slider(
label="Top-k",
info="← less unconfident tokens | more unconfident tokens →",
maximum=150,
minimum=0,
step=1,
value=80,
)
aspect_ratio_place_holder = gr.Number(value=1.0, visible=False)
prompt_gen.click(
self.prompt_gen_only,
inputs=[
self.tag_prompt_area[is_img2img],
self.prompt_area[is_img2img * 2 + 1],
aspect_ratio_place_holder,
seed_num_input,
tag_length_radio,
nl_length_radio,
ban_tags_textbox,
format_dropdown,
format_textarea,
temperature_slider,
top_p_slider,
top_k_slider,
model_dropdown,
gguf_use_cpu,
no_formatting,
self.tag_prompt_area[is_img2img],
],
outputs=[
self.prompt_area[is_img2img * 2],
],
)
self.infotext_fields = [
(
(tipo_acc, lambda d: gr.update(value=INFOTEXT_KEY in d))
if InputAccordion
else (tipo_acc, lambda d: gr.update(open=INFOTEXT_KEY in d))
),
(
self.prompt_area[is_img2img * 2],
lambda d: d.get(INFOTEXT_KEY_PROMPT, d["Prompt"]),
),
(
self.prompt_area[is_img2img * 2 + 1],
lambda d: d.get(INFOTEXT_NL_PROMPT, ""),
),
(orig_prompt_area, lambda d: d["Prompt"]),
(enabled_check, lambda d: INFOTEXT_KEY in d),
(seed_num_input, lambda d: self.get_infotext(d, "seed", None)),
(tag_length_radio, lambda d: self.get_infotext(d, "tag_length", None)),
(nl_length_radio, lambda d: self.get_infotext(d, "nl_length", None)),
(ban_tags_textbox, lambda d: self.get_infotext(d, "ban_tags", None)),
(format_dropdown, lambda d: self.get_infotext(d, "format", None)),
(format_textarea, lambda d: d.get(INFOTEXT_KEY_FORMAT, None)),
(
process_timing_dropdown,
lambda d: PROCESSING_TIMING.get(
self.get_infotext(d, "timing", None), None
),
),
(temperature_slider, lambda d: self.get_infotext(d, "temperature", None)),
(top_p_slider, lambda d: self.get_infotext(d, "top_p", None)),
(top_k_slider, lambda d: self.get_infotext(d, "top_k", None)),
(
model_dropdown,
lambda d: self.get_infotext(d, "model", None),
),
(gguf_use_cpu, lambda d: self.get_infotext(d, "gguf_cpu", None)),
(no_formatting, lambda d: self.get_infotext(d, "no_formatting", None)),
(
use_generation_seed,
lambda d: self.get_infotext(d, "follow_generation_seed", None),
),
]
# use_generation_seed sits right after the seed it overrides, so every
# later position keeps the index write_infotext and _process expect.
return [
enabled_check,
process_timing_dropdown,
seed_num_input,
use_generation_seed,
tag_length_radio,
nl_length_radio,
ban_tags_textbox,
format_dropdown,
format_textarea,
temperature_slider,
top_p_slider,
top_k_slider,
model_dropdown,
gguf_use_cpu,
no_formatting,
self.tag_prompt_area[is_img2img],
self.prompt_area[is_img2img * 2 + 1],
]
def get_tipo_seed_from_generation_info(self, gen_info_string: str, index: int = 0):
"""
Retrieves TIPO seed from generation_info JSON data.
For batch generation, retrieves the seed for the selected image using the index.
Args:
gen_info_string: JSON string of generation_info
index: Gallery index (default: 0) - which image in batch to get seed from
Returns:
int: TIPO seed value, or -1 if not found or on error
"""
res = -1
try:
gen_info = json.loads(gen_info_string)
# Get infotext for the specific image index
infotexts = gen_info.get("infotexts", [])
if index < len(infotexts):
infotext = infotexts[index]
# Parse generation parameters from infotext
gen_parameters = infotext_utils.parse_generation_parameters(
infotext, []
)
# Get TIPO Parameters string from parsed parameters
tipo_params_str = gen_parameters.get("TIPO Parameters", "")
if tipo_params_str:
# Convert JavaScript boolean literals to Python
dict_string = tipo_params_str.replace("false", "False").replace(
"true", "True"
)
# Parse as dictionary
tipo_params = ast.literal_eval(dict_string)
# Get seed value
res = int(tipo_params.get("seed", -1))
else:
# Fallback to extra_generation_params if index is out of range
extra_params = gen_info.get("extra_generation_params", {})
tipo_params_str = extra_params.get("TIPO Parameters", "")
if tipo_params_str:
dict_string = tipo_params_str.replace("false", "False").replace(
"true", "True"
)
tipo_params = ast.literal_eval(dict_string)
res = int(tipo_params.get("seed", -1))
except Exception as e:
if gen_info_string:
logger.warning(f"Error retrieving TIPO seed from generation info: {e}")
return res
def get_infotext(self, d, target, default):
return d.get(INFOTEXT_KEY, {}).get(target, default)
def write_infotext(
self,
p: StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img,
prompt: str,
process_timing: str,
seed: int,
follow_generation_seed: bool,
*args,
):
p.extra_generation_params[INFOTEXT_KEY] = json.dumps(
{
"seed": seed,
"follow_generation_seed": follow_generation_seed,
"timing": process_timing,
"tag_length": args[0],
"nl_length": args[1],
"ban_tags": args[2],
"format_selected": args[3],
"format": args[4],
"temperature": args[5],
"top_p": args[6],
"top_k": args[7],
"model": args[8],
"gguf_cpu": args[9],
"no_formatting": args[10],
},
ensure_ascii=False,
).translate(QUOTESWAP)
p.extra_generation_params[INFOTEXT_KEY_PROMPT] = prompt.strip() or args[-1]
p.extra_generation_params[INFOTEXT_NL_PROMPT] = args[-2]
if args[3] != DEFAULT_FORMAT:
p.extra_generation_params[INFOTEXT_KEY_FORMAT] = args[3]
def process(
self,
p: StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img,
is_enabled: bool,
process_timing: str,
seed: int,
follow_generation_seed: bool,
*args,
):
"""This method will be called after sd-dynamic-prompts and the styles are applied."""
if not is_enabled:
return
if process_timing != PROCESSING_TIMING["AFTER"]:
return
self.original_prompt = p.all_prompts
self.original_hr_prompt = getattr(p, "all_hr_prompts", None)
aspect_ratio = p.width / p.height
if seed == -1:
seed = random.randrange(2**31 - 1)
seed = int(seed)
args = list(args)
if args[3] != "custom":
args[4] = TIPO_DEFAULT_FORMAT.get(args[3], args[4])
self.write_infotext(p, p.prompt, "AFTER", seed, follow_generation_seed, *args)
args = list(args)
nl_prompt = args.pop()
new_all_prompts = []
for prompt, sub_seed in zip(p.all_prompts, p.all_seeds):
# Following the image seed keeps each batch item's prompt tied to the
# image it produces (issue #14).
tipo_seed = int(sub_seed) if follow_generation_seed else seed + sub_seed
new_all_prompts.append(
self._process(prompt, nl_prompt, aspect_ratio, tipo_seed, *args)
)
hr_fix_enabled = getattr(p, "enable_hr", False)
if hr_fix_enabled:
if p.hr_prompt != p.prompt:
new_hr_prompts = []
for prompt, hr_prompt in zip(p.all_prompts, p.all_hr_prompts):
if prompt == hr_prompt:
new_hr_prompts.append(prompt)
else:
new_hr_prompts.append(hr_prompt)
p.all_hr_prompts = new_hr_prompts
else:
p.all_hr_prompts = new_all_prompts
p.all_prompts = new_all_prompts
def before_process(
self,
p: StableDiffusionProcessingTxt2Img | StableDiffusionProcessingImg2Img,
is_enabled: bool,
process_timing: str,
seed: int,
follow_generation_seed: bool,
*args,
):
"""This method will be called before sd-dynamic-prompts and the styles are applied."""
if not is_enabled:
return
if process_timing != PROCESSING_TIMING["BEFORE"]:
return
self.original_prompt = p.prompt
self.original_hr_prompt = p.hr_prompt
aspect_ratio = p.width / p.height
if seed == -1:
seed = random.randrange(4294967294)
self.write_infotext(p, p.prompt, "BEFORE", seed, follow_generation_seed, *args)
fix_seed(p)
seed = int(p.seed) if follow_generation_seed else int(seed + p.seed)
args = list(args)
p.prompt = self._process(p.prompt, args.pop(), aspect_ratio, seed, *args)
def prompt_gen_only(self, *args):
args = list(args)
seed = args[3]
if seed == -1:
seed = random.randrange(2**31 - 1)
args[3] = seed
return self._process(*args)
def _process(
self,
prompt: str,
nl_prompt: str,
aspect_ratio: float,
seed: int,
tag_length: str,
nl_length: str,
ban_tags: str,
format_select: str,
format: str,
temperature: float,
top_p: float,
top_k: int,
model: str,
gguf_use_cpu: bool,
no_formatting: bool,
tag_prompt: str,
):
prompt = prompt.strip() or tag_prompt
seed = int(seed) % SEED_MAX
if model != self.current_model:
ensure_runtime()
if " | " in model:
model_name, gguf_name = model.split(" | ")
target_file = f"{model_name.split('/')[-1]}_{gguf_name}"
if str(models.model_dir / target_file) not in models.list_gguf():
models.download_gguf(model_name, gguf_name)
target = os.path.join(str(models.model_dir), target_file)
gguf = True
else:
target = model
gguf = False
target_device = "cpu" if gguf_use_cpu else str(devices.device)
models.load_model(target, gguf, device=target_device)
self.current_model = model
prompt = strip_comments(prompt)
nl_prompt = strip_comments(nl_prompt)
prompt_preview = prompt.replace("\n", " ")[:40]
logger.info(f"Processing prompt: {prompt_preview}...")
logger.info(f"Processing with seed: {seed}")
prompt_without_extranet, res = parse_prompt(prompt)
prompt_parse_strength = parse_prompt_attention(prompt_without_extranet)
nl_prompt_parse_strength = parse_prompt_attention(nl_prompt)
nl_prompt = ""
strength_map_nl = []
for part, strength in nl_prompt_parse_strength:
nl_prompt += part
if strength == 1:
continue
strength_map_nl.append((part, strength))
rebuild_extranet = ""
for name, params in res.items():
for param in params:
items = ":".join(param.items)
rebuild_extranet += f" <{name}:{items}>"
black_list = [tag.strip() for tag in ban_tags.split(",") if tag.strip()]
tipo.BAN_TAGS = black_list
all_tags = []
strength_map = {}
break_map = set()
for part, strength in prompt_parse_strength:
part_tags = [tag.strip() for tag in part.strip().split(",") if tag.strip()]
if part == "BREAK" and strength == -1:
break_map.add(all_tags[-1])
continue
all_tags.extend(part_tags)
if strength == 1:
continue
for tag in part_tags:
strength_map[tag] = strength
tag_length = tag_length.replace(" ", "_")
nl_length = nl_length.replace(" ", "_")
embeddings = embedding_spellings(all_tags)
org_tag_map = restore_embeddings(seperate_tags(all_tags), embeddings)
meta, operations, general, nl_prompt = parse_tipo_request(
org_tag_map,
nl_prompt,
tag_length_target=tag_length,
nl_length_target=nl_length,
generate_extra_nl_prompt=(not nl_prompt and "<|extended|>" in format)
or "<|generated|>" in format,
)
meta["aspect_ratio"] = f"{aspect_ratio:.1f}"
if isinstance(models.text_model, torch.nn.Module):
models.text_model.to(devices.device)
tag_map, _ = tipo_runner(
meta,
operations,
general,
nl_prompt,
temperature=temperature,
top_p=top_p,
top_k=top_k,
seed=seed,
)
if isinstance(models.text_model, torch.nn.Module):
models.text_model.cpu()
devices.torch_gc()
tag_map = restore_embeddings(tag_map, embeddings)
addon = {
"tags": [],
"nl": "",
}
for cate in tag_map:
if cate == "generated" and addon["nl"] == "":
addon["nl"] = tag_map[cate]
continue
if cate == "extended":
extended = tag_map[cate]
addon["nl"] = extended
continue
if cate not in org_tag_map:
continue
for tag in tag_map[cate]:
if tag in org_tag_map[cate]:
continue
addon["tags"].append(tag)
addon = apply_strength(addon, strength_map, strength_map_nl, break_map)
unformatted_prompt_by_tipo = (
prompt_without_extranet
+ ", "
+ ", ".join(addon["tags"])
+ "\n"
+ addon["nl"]
)
tag_map = apply_strength(tag_map, strength_map, strength_map_nl, break_map)
formatted_prompt_by_tipo = apply_format(tag_map, format).replace(
"BREAK,", "BREAK"
)
if no_formatting:
final_prompt = unformatted_prompt_by_tipo
else:
final_prompt = formatted_prompt_by_tipo
result = final_prompt + "\n" + rebuild_extranet
logger.info("Prompt processing done.")
return result
def parse_infotext(_, params):
try:
params[INFOTEXT_KEY] = json.loads(params[INFOTEXT_KEY].translate(QUOTESWAP))
except Exception:
pass
scripts.script_callbacks.on_infotext_pasted(parse_infotext)
options.categories.register_category("prompt_gen", "Prompt Gen")
shared.options_templates.update(
shared.options_section(
("TIPO", "TIPO", "prompt_gen"),
{
"tipo_no_extra_input": shared.OptionInfo(
False,
(
"Disable extra input for TIPO"
", Natural Language Prompt and Tag Prompt will be hidden."
" (UI Reload Needed)"
),
),
"tipo_extra_input_layout": shared.OptionInfo(
EXTRA_INPUT_LAYOUT[0],
"Layout for the TIPO prompt inputs (UI Reload Needed)",
gr.Radio,
{"choices": EXTRA_INPUT_LAYOUT},
),
},
)
)