Add wildcardsPromptMatrix Node:
1. wildcard-replaced prompt will be returned in order rather than randomly 2. will return the amount of probilities and amount of probilities each option or wildcard 3. the prompt can be selected by offset argument, the order of probilities is fixed 4. even if the offset exceeds the total, it will not stop, but will always return to the last probility, requiring additional nodes to control.
This commit is contained in:
@@ -145,6 +145,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy wildcardsMatrix": {
|
||||
"display_name": "Wildcard Matrix",
|
||||
"inputs": {
|
||||
"Select to add LoRA": {
|
||||
"name": "Select to add LoRA"
|
||||
},
|
||||
"Select to add Wildcard": {
|
||||
"name": "Select to add Wildcard"
|
||||
},
|
||||
"offset": {
|
||||
"name": "Offset in All Probilities"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "Prompt"
|
||||
},
|
||||
"1": {
|
||||
"name": "Replaced Prompt"
|
||||
},
|
||||
"2": {
|
||||
"name": "Total Count of the Probilities"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy prompt": {
|
||||
"display_name": "Prompt",
|
||||
"inputs": {
|
||||
|
||||
@@ -75,6 +75,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy wildcardsMatrix": {
|
||||
"display_name": "通配符提示词矩阵",
|
||||
"inputs": {
|
||||
"Select to add LoRA": {
|
||||
"name": "选择添加Lora"
|
||||
},
|
||||
"Select to add Wildcard": {
|
||||
"name": "选择添加通配符"
|
||||
},
|
||||
"offset": {
|
||||
"name": "偏移量"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "提示词"
|
||||
},
|
||||
"1": {
|
||||
"name": "通配填充词"
|
||||
},
|
||||
"2": {
|
||||
"name": "可能性总数"
|
||||
}
|
||||
}
|
||||
},
|
||||
"easy prompt": {
|
||||
"display_name": "提示词",
|
||||
"inputs": {
|
||||
|
||||
+183
-5
@@ -1,9 +1,13 @@
|
||||
import re
|
||||
import random
|
||||
import os
|
||||
import folder_paths
|
||||
import yaml
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from math import prod
|
||||
|
||||
import yaml
|
||||
|
||||
import folder_paths
|
||||
|
||||
from .log import log_node_info
|
||||
|
||||
easy_wildcard_dict = {}
|
||||
@@ -302,3 +306,177 @@ def process_with_loras(wildcard_opt, model, clip, title="Positive", seed=None, c
|
||||
log_node_info("easy wildcards",f'{title}_decode: {pass1}')
|
||||
|
||||
return model, clip, pass2, pass1, show_wildcard_prompt, pipe_lora_stack
|
||||
|
||||
|
||||
def expand_wildcard(keyword: str) -> tuple[str]:
|
||||
"""传入文件通配符的关键词,从 easy_wildcard_dict 中获取通配符的所有选项。"""
|
||||
global easy_wildcard_dict
|
||||
if keyword in easy_wildcard_dict:
|
||||
return tuple(easy_wildcard_dict[keyword])
|
||||
elif '*' in keyword:
|
||||
subpattern = keyword.replace('*', '.*').replace('+', r"\+")
|
||||
total_pattern = []
|
||||
for k, v in easy_wildcard_dict.items():
|
||||
if re.match(subpattern, k) is not None:
|
||||
total_pattern.extend(v)
|
||||
if total_pattern:
|
||||
return tuple(total_pattern)
|
||||
elif '/' not in keyword:
|
||||
return expand_wildcard(f"*/{keyword}")
|
||||
|
||||
def expand_options(options: str) -> tuple[str]:
|
||||
"""传入去掉 {} 的选项。
|
||||
展开选项通配符,返回该选项中的每一项,这里的每一项都是一个替换项。
|
||||
不会对选项内容进行任何处理,即便存在空格或特殊符号,也会原样返回。"""
|
||||
return tuple(options.split("|"))
|
||||
|
||||
|
||||
def decimal_to_irregular(n, bases):
|
||||
"""
|
||||
将十进制数转换为不规则进制
|
||||
|
||||
:param n: 十进制数
|
||||
:param bases: 各位置的基数列表,从低位到高位
|
||||
:return: 不规则进制表示的列表,从低位到高位
|
||||
"""
|
||||
if n == 0:
|
||||
return [0] * len(bases) if bases else [0]
|
||||
|
||||
digits = []
|
||||
remaining = n
|
||||
|
||||
# 从低位到高位处理
|
||||
for base in bases:
|
||||
digit = remaining % base
|
||||
digits.append(digit)
|
||||
remaining = remaining // base
|
||||
|
||||
return digits
|
||||
|
||||
|
||||
class WildcardProcessor:
|
||||
"""通配符处理器
|
||||
|
||||
通配符格式:
|
||||
+ option : {a|b}
|
||||
+ wildcard: __dir/filename__ filename 不应添加后缀,会自动补全为 .txt
|
||||
"""
|
||||
|
||||
RE_OPTIONS = re.compile(r"{([^{}]*?)}")
|
||||
RE_WILDCARD = re.compile(r"__([\w\s.\-+/*\\]+?)__")
|
||||
RE_REPLACER = re.compile(r"{([^{}]*?)}|__([\w\s.\-+/*\\]+?)__")
|
||||
|
||||
# 将输入的提示词转化成符合 python str.format 要求格式的模板,并将 option 和 wildcard 按照顺序在模板中留下 {0}, {1} 等占位符
|
||||
template: str
|
||||
# option、wildcard 的替换项列表,按照在模板中出现的顺序排列,相同的替换项列表只保留第一份
|
||||
replacers: dict[int, tuple[str]]
|
||||
# 占位符的编号和替换项列表的索引的映射,占位符编号按照在模板中出现的顺序排列,方便减少替换项的存储占用
|
||||
placeholder_mapping: dict[str, int] # placeholder_id => replacer_id
|
||||
# 各替换项列表的项数,按照在模板中出现的顺序排列,提前计算,方便后续使用
|
||||
placeholder_choices: dict[str, int] # placeholder_id => len(replacer)
|
||||
|
||||
def __init__(self, text: str):
|
||||
self.__make_template(text)
|
||||
self.__total = None
|
||||
|
||||
def random(self, seed=None) -> str:
|
||||
"从所有可能性中随机获取一个"
|
||||
if seed is not None:
|
||||
random.seed(seed)
|
||||
return self.getn(random.randint(0, self.total() - 1))
|
||||
|
||||
def getn(self, n: int) -> str:
|
||||
"从所有可能性中获取第 n 个"
|
||||
if n >= self.total():
|
||||
return self.getn(self.total() - 1)
|
||||
if n < 0:
|
||||
return self.getn(0)
|
||||
|
||||
indice = decimal_to_irregular(n, self.placeholder_choices.values())
|
||||
replacements = {
|
||||
placeholder_id: self.replacers[self.placeholder_mapping[placeholder_id]][i]
|
||||
for placeholder_id, i in zip(self.placeholder_mapping.keys(), indice)
|
||||
}
|
||||
return self.template.format(**replacements)
|
||||
|
||||
def getmany(self, limit: int, offset: int = 0) -> list[str]:
|
||||
"""返回一组可能性组成的列表,为了避免结果太长导致内存占用超限,使用 limit 限制列表的长度,使用 offset 调整偏移。
|
||||
若 limit 和 offset 的设置导致预期的结果长度超过剩下的实际长度,则结果会被截断为实际长度。
|
||||
"""
|
||||
if offset + limit > self.total():
|
||||
limit = self.total() - offset
|
||||
return [self.getn(n) for n in range(offset, offset + limit)]
|
||||
|
||||
def total(self) -> int:
|
||||
"计算可能性的数目"
|
||||
if self.__total is None:
|
||||
self.__total = prod(self.placeholder_choices.values())
|
||||
return self.__total
|
||||
|
||||
def __make_template(self, text: str):
|
||||
"""将输入的提示词转化成符合 python str.format 要求格式的模板,
|
||||
并将 option 和 wildcard 按照顺序在模板中留下 {0}, {1} 等占位符,
|
||||
即使遇到相同的 option 或 wildcard,留下的占位符编号也不同,从而使每项都独立变化。
|
||||
"""
|
||||
self.placeholder_mapping = {}
|
||||
placeholder_id = 0
|
||||
replacer_id = 0
|
||||
replacers_rev = {} # replacers => id
|
||||
blocks = []
|
||||
# 记录所处理过的通配符末尾在文本中的位置,用于拼接完整的模板
|
||||
tail = 0
|
||||
for match in self.RE_REPLACER.finditer(text):
|
||||
# 提取并展开通配符内容
|
||||
m = match.group(0)
|
||||
if m.startswith("{"):
|
||||
choices = expand_options(m[1:-1])
|
||||
elif m.startswith("__"):
|
||||
keyword = m[2:-2].lower()
|
||||
keyword = wildcard_normalize(keyword)
|
||||
choices = expand_wildcard(keyword)
|
||||
else:
|
||||
raise ValueError(f"{m!r} is not a wildcard or option")
|
||||
|
||||
# 记录通配符的替换项列表和ID,相同的通配符只保留第一个
|
||||
if choices not in replacers_rev:
|
||||
replacers_rev[choices] = replacer_id
|
||||
replacer_id += 1
|
||||
|
||||
# 拼接通配符前方文本
|
||||
start, end = match.span()
|
||||
blocks.append(text[tail:start])
|
||||
tail = end
|
||||
# 将通配符替换为占位符,并记录占位符和替换项列表的索引的映射
|
||||
blocks.append(f"{{r{placeholder_id}}}")
|
||||
self.placeholder_mapping[f"r{placeholder_id}"] = replacers_rev[choices]
|
||||
placeholder_id += 1
|
||||
|
||||
if tail < len(text):
|
||||
blocks.append(text[tail:])
|
||||
self.template = "".join(blocks)
|
||||
self.replacers = {v: k for k, v in replacers_rev.items()}
|
||||
self.placeholder_choices = {
|
||||
placeholder_id: len(self.replacers[replacer_id])
|
||||
for placeholder_id, replacer_id in self.placeholder_mapping.items()
|
||||
}
|
||||
|
||||
|
||||
def test_option():
|
||||
text = "{|a|b|c}"
|
||||
answer = ["", "a", "b", "c"]
|
||||
p = WildcardProcessor(text)
|
||||
assert p.total() == len(answer)
|
||||
assert p.getn(0) == answer[0]
|
||||
assert p.getmany(4) == answer
|
||||
assert p.getmany(4, 1) == answer[1:]
|
||||
|
||||
|
||||
def test_same():
|
||||
text = "{a|b},{a|b}"
|
||||
answer = ["a,a", "b,a", "a,b", "b,b"]
|
||||
p = WildcardProcessor(text)
|
||||
assert p.total() == len(answer)
|
||||
assert p.getn(0) == answer[0]
|
||||
assert p.getmany(4) == answer
|
||||
assert p.getmany(4, 1) == answer[1:]
|
||||
|
||||
|
||||
+61
-6
@@ -1,12 +1,15 @@
|
||||
import os
|
||||
import json
|
||||
import folder_paths
|
||||
import os
|
||||
from urllib.request import urlopen
|
||||
from ..libs.log import log_node_info
|
||||
from ..libs.wildcards import get_wildcard_list, process
|
||||
from ..libs.utils import AlwaysEqualProxy
|
||||
from ..config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, MAX_SEED_NUM, PROMPT_TEMPLATE
|
||||
|
||||
import folder_paths
|
||||
|
||||
from .. import easyCache
|
||||
from ..config import FOOOCUS_STYLES_DIR, MAX_SEED_NUM, PROMPT_TEMPLATE, RESOURCES_DIR
|
||||
from ..libs.log import log_node_info
|
||||
from ..libs.utils import AlwaysEqualProxy
|
||||
from ..libs.wildcards import WildcardProcessor, get_wildcard_list, process
|
||||
|
||||
|
||||
# 正面提示词
|
||||
class positivePrompt:
|
||||
@@ -83,6 +86,56 @@ class wildcardsPrompt:
|
||||
text = [text]
|
||||
return {"ui": {"value": [seed]}, "result": (text, populated_text)}
|
||||
|
||||
# 通配符提示词矩阵,会按顺序返回包含通配符的提示词所生成的所有可能
|
||||
class wildcardsPromptMatrix:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
# wildcard_list = get_wildcard_list()
|
||||
return {"required": {
|
||||
# 包含通配符的提示词模板
|
||||
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
|
||||
# 这个新节点无法通过下拉选单直接向 text 中添加 lora 或 wildcard,为什么?
|
||||
# # 点击该属性从下拉列表中选择要添加的lora
|
||||
# "Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
|
||||
# # 点击该属性从下拉列表中选择要添加的通配符文件
|
||||
# "Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
|
||||
# 本次返回第 n 种可能
|
||||
"offset": ("INT", {"default": 0, "min": 0, "step": 1, "control_after_generate": True}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING", "INT", "INT")
|
||||
# 提示词、替换后提示词、可能性的总数、各替换项的可能性数目
|
||||
RETURN_NAMES = ("text", "populated_text", "total", "factors")
|
||||
OUTPUT_IS_LIST = (True, True, True, True)
|
||||
FUNCTION = "main"
|
||||
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
def translate(self, text):
|
||||
return text
|
||||
|
||||
|
||||
def main(self, *args, **kwargs):
|
||||
prompt = kwargs["prompt"] if "prompt" in kwargs else None
|
||||
offset = kwargs["offset"]
|
||||
|
||||
# Clean loaded_objects
|
||||
if prompt:
|
||||
easyCache.update_loaded_objects(prompt)
|
||||
|
||||
text = kwargs['text']
|
||||
text = self.translate(text)
|
||||
p = WildcardProcessor(text)
|
||||
populated_text = [p.getn(offset)]
|
||||
text = [text]
|
||||
return {"ui": {"value": [offset]}, "result": (text, populated_text, [p.total()], list(p.placeholder_choices.values()))}
|
||||
|
||||
# 负面提示词
|
||||
class negativePrompt:
|
||||
|
||||
@@ -518,6 +571,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy positive": positivePrompt,
|
||||
"easy negative": negativePrompt,
|
||||
"easy wildcards": wildcardsPrompt,
|
||||
"easy wildcardsMatrix": wildcardsPromptMatrix,
|
||||
"easy prompt": prompt,
|
||||
"easy promptList": promptList,
|
||||
"easy promptLine": promptLine,
|
||||
@@ -531,6 +585,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy positive": "Positive",
|
||||
"easy negative": "Negative",
|
||||
"easy wildcards": "Wildcards",
|
||||
"easy wildcardsMatrix": "Wildcards Matrix",
|
||||
"easy prompt": "Prompt",
|
||||
"easy promptList": "PromptList",
|
||||
"easy promptLine": "PromptLine",
|
||||
|
||||
Reference in New Issue
Block a user