diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3ba4d..8209f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.5.0 + +- Added `Multi Vocab String List (Nukun)` for combining four selectable vocabulary files with deterministic random fills and optional direct word selectors. +- Added `resources/camera_composition.csv` for camera, framing, focus, perspective, and composition prompt terms. + ## v0.4.0 - Added deduplicated `little_doom_*.csv` Illustrious LoRA keyword resources and a reproducible generator utility. diff --git a/README.md b/README.md index d8ba937..61d4075 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ The main node set and core examples work with a standard ComfyUI installation. T - `NukunCheckpointPairCyclerLoader` - display name `Checkpoint Pair Cycler Loader (Nukun)`, category `Nukun/Loaders` - `NukunIncrementingIntString` - display name `Incrementing Int to String (Nukun)`, category `Nukun/Text` - `NukunRandomVocabStringList` - display name `Random Vocab String List (Nukun)`, category `Nukun/Text` +- `NukunVocabMultiStringList` - display name `Multi Vocab String List (Nukun)`, category `Nukun/Text` +- `NukunOllamaPromptRefiner` - display name `Ollama Prompt Refiner (Nukun)`, category `Nukun/Text` - `LoadImagewithSubfolders` - display name `Load Image with Subfolders`, category `Nukun/Image` - `T5Balancer` - display name `T5 Token-based Prompt Balancer`, category `Nukun/Conditioning` - `NukunT5EqualLengthBalancer` - display name `T5 Equal-Length Prompt Balancer (Nukun)`, category `Nukun/Conditioning` @@ -96,10 +98,25 @@ This node reads a selected comma-separated plain-text word list and outputs a de It can use `ComfyUI/user/vocab.json` or bundled files such as `resources/english_words.csv`; add more `.csv`, `.txt`, or `.json` files to `resources/` to make them selectable. Set `amount` for the number of words and use the `seed` widget's control-after-generate behavior to keep, increment, decrement, or randomize selections between queued runs. Words are sampled without duplicates; if `amount` is larger than the available vocabulary, the output is clamped to the full list. -Bundled category vocabularies include places/environments, objects, animals and mythical creatures, verbs, nouns, adjectives, `*ing` words, person names, countries, and cities. +Bundled category vocabularies include places/environments, objects, animals and mythical creatures, verbs, nouns, adjectives, `*ing` words, camera/composition terms, person names, countries, and cities. Bundled Little Doom LoRA keyword resources are also available as `little_doom_*.csv`, including clean, character/source, visual feature, clothing, action, setting, style, mature, and dark/gore subsets. Connect the optional `chain` input to append generated words after an existing string and build prompt chains. +`Multi Vocab String List (Nukun)` combines four selectable vocab files in one node. Each slot has its own amount and three optional direct word selectors; keep a selector on `(random)` to fill it from the chosen vocabulary. The node returns a combined string plus one output per slot. + +## Ollama Prompt Refiner + +This node sends a random vocabulary string to a local Ollama model and returns one curated prompt pair for the selected `target_profile`: `pony_v6`, `illustrious`, or `pony_v7`. +It is designed to sit after `Random Vocab String List (Nukun)` or `Multi Vocab String List (Nukun)`: connect the random `STRING` output to `word_salad`, choose one `target_profile`, then connect `positive` and `negative` to your text encoders. +The default Ollama endpoint is `http://127.0.0.1:11434` and the default model is `autoren-darkidol-llama-3-1-8b:latest`. +The `ollama_model` widget is a dropdown populated from the local Ollama `/api/tags` list when ComfyUI loads the node. +Set `style_cluster` to choose the Pony v7 `style_cluster_XXXX` header value; the default is `430`. +Use `style_anchor` for fixed character names, LoRA trigger words, quality tags, or motifs that should survive the rewrite even when the random input is noisy. +The node outputs `positive`, `negative`, and `report`. Pony v6 and Illustrious outputs are normalized without commas to reduce prompt tokens; Pony v7 keeps commas for its structured caption and Danbooru tag block. +Reka Flash models are sent with their `human: ... assistant:` prompt format and ``/`<|endoftext|>` stop strings because the GGUF import uses a plain prompt template. +The node asks Ollama for strict JSON and retries once with a repair prompt if the model returns malformed JSON. +This is a breaking output change from older versions that exposed separate `pony_v6_*`, `illustrious_*`, and `pony_v7_*` outputs; reconnect old workflows to `positive` and `negative`. + ## Regional Split Regions This node creates simple horizontal or vertical 2/3-way split masks and returns A8R8 `AttentionCouple` compatible regions. diff --git a/Z-Image Promptgestaltung.pdf b/Z-Image Promptgestaltung.pdf new file mode 100644 index 0000000..faede4c Binary files /dev/null and b/Z-Image Promptgestaltung.pdf differ diff --git a/__init__.py b/__init__.py index 50533d2..308a9ac 100644 --- a/__init__.py +++ b/__init__.py @@ -6,6 +6,8 @@ from .nodes.incrementing_int_string import NODE_CLASS_MAPPINGS as INT_STRING_CLA from .nodes.incrementing_int_string import NODE_DISPLAY_NAME_MAPPINGS as INT_STRING_DISPLAY_MAPPINGS from .nodes.random_vocab_string_list import NODE_CLASS_MAPPINGS as RANDOM_VOCAB_STRING_LIST_CLASS_MAPPINGS from .nodes.random_vocab_string_list import NODE_DISPLAY_NAME_MAPPINGS as RANDOM_VOCAB_STRING_LIST_DISPLAY_MAPPINGS +from .nodes.ollama_prompt_refiner import NODE_CLASS_MAPPINGS as OLLAMA_PROMPT_REFINER_CLASS_MAPPINGS +from .nodes.ollama_prompt_refiner import NODE_DISPLAY_NAME_MAPPINGS as OLLAMA_PROMPT_REFINER_DISPLAY_MAPPINGS from .nodes.t5_token_balancer import NODE_CLASS_MAPPINGS as T5_CLASS_MAPPINGS from .nodes.t5_token_balancer import NODE_DISPLAY_NAME_MAPPINGS as T5_DISPLAY_MAPPINGS from .nodes.t5_equal_length_balancer import NODE_CLASS_MAPPINGS as T5_EQUAL_LENGTH_CLASS_MAPPINGS @@ -49,6 +51,7 @@ NODE_CLASS_MAPPINGS = {} NODE_CLASS_MAPPINGS.update(CHECKPOINT_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(INT_STRING_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(RANDOM_VOCAB_STRING_LIST_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(OLLAMA_PROMPT_REFINER_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(LOAD_IMAGE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(T5_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(T5_EQUAL_LENGTH_CLASS_MAPPINGS) @@ -73,6 +76,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS.update(CHECKPOINT_DISPLAY_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(INT_STRING_DISPLAY_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(RANDOM_VOCAB_STRING_LIST_DISPLAY_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(OLLAMA_PROMPT_REFINER_DISPLAY_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(LOAD_IMAGE_DISPLAY_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(T5_DISPLAY_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(T5_EQUAL_LENGTH_DISPLAY_MAPPINGS) diff --git a/nodes/ollama_prompt_refiner.py b/nodes/ollama_prompt_refiner.py new file mode 100644 index 0000000..49dd59c --- /dev/null +++ b/nodes/ollama_prompt_refiner.py @@ -0,0 +1,2148 @@ +import hashlib +import json +import re +import urllib.error +import urllib.request + + +DEFAULT_OLLAMA_URL = "http://127.0.0.1:11434" +DEFAULT_OLLAMA_MODEL = "autoren-darkidol-llama-3-1-8b:latest" +DEFAULT_OLLAMA_CONTEXT_LENGTH = 8192 +DEFAULT_STYLE_CLUSTER = 430 +SPLIT_BASE_WORD_RANGE = (10, 20) +SPLIT_DETAIL_WORD_RANGE = (36, 40) +Z_IMAGE_POSITIVE_WORD_RANGE = (360, 440) +Z_IMAGE_FOREGROUND_MIN_WORDS = 130 +Z_IMAGE_BACKGROUND_MIN_WORDS = 130 +Z_IMAGE_BASE_MIN_WORDS = 80 +TARGET_PROFILES = ("pony_v6", "illustrious", "pony_v7", "z_image") +DEFAULT_TARGET_PROFILE = "pony_v7" + +OUTPUT_KEYS = ( + "positive", + "negative", + "report", + "base_prompt", + "foreground_prompt", + "background_prompt", +) + +RESPONSE_KEYS = ( + "base_prompt", + "foreground_prompt", + "background_prompt", + "negative", + "report", +) + +LEGACY_RESPONSE_KEYS = ( + "positive", + "negative", + "report", +) + +OUTPUT_SCHEMA = { + "type": "object", + "properties": { + key: {"type": "string", "minLength": 0 if key == "negative" else 1} + for key in RESPONSE_KEYS + }, + "required": list(RESPONSE_KEYS), + "additionalProperties": False, +} + +EQUINE_PATTERN = re.compile(r"\b(pony|ponies|horse|horses|equine|stallion|mare|foal)\b", re.IGNORECASE) +MODEL_LABEL_PATTERN = re.compile(r"\bpony[\s_-]+v?\d+\b", re.IGNORECASE) +MODEL_VERSION_LEAK_PATTERN = re.compile(r"\b(character|subject)[\s:_-]+v?\d+(?:_sdxl)?\b", re.IGNORECASE) + +NEGATIVE_BASELINES = { + "pony_v6": ( + "low quality", + "worst quality", + "bad anatomy", + "bad hands", + "malformed fingers", + "extra fingers", + "missing fingers", + "deformed body", + "duplicate character", + "text", + "watermark", + "logo", + "jpeg artifacts", + "blurry", + ), + "illustrious": ( + "low quality", + "worst quality", + "bad anatomy", + "bad hands", + "malformed fingers", + "extra fingers", + "missing fingers", + "poorly drawn face", + "duplicate character", + "text", + "watermark", + "logo", + "jpeg artifacts", + "blurry", + ), + "pony_v7": ( + "low resolution", + "distorted hands", + "extra fingers", + "missing fingers", + "broken limbs", + "messy composition", + "overexposed image", + "blurry face", + "duplicated character", + "unreadable text", + "watermark", + "logo", + "compression artifacts", + ), + "z_image": (), +} + +NEGATIVE_MARKERS = ( + "low quality", + "worst quality", + "bad anatomy", + "bad hands", + "malformed", + "deformed", + "artifact", + "watermark", + "blurry", + "text", +) + +STOPWORDS = { + "a", + "an", + "and", + "are", + "as", + "at", + "background", + "be", + "behind", + "by", + "danbooru", + "define", + "description", + "enabling", + "factual", + "film_grain", + "foreground", + "front", + "for", + "from", + "her", + "hers", + "his", + "image", + "in", + "into", + "is", + "it", + "its", + "medium", + "model", + "of", + "on", + "or", + "remove_background", + "render", + "rendered", + "shows", + "stylistic", + "that", + "their", + "them", + "the", + "this", + "to", + "tags", + "test", + "with", +} + +LOW_VALUE_PROMPT_TERMS = { + "ac_unit", + "ability", + "artificial", + "baby_bottle", + "balanced_composition", + "best", + "best_quality", + "charger_cable", + "change", + "clay", + "clean_lineart", + "creative", + "detailed_background", + "detailed_character_design", + "dynamic_pose", + "enable", + "enabled", + "enabling", + "expressive_face", + "high_detail", + "masterpiece", + "remove", + "removed", + "remove_background", + "backgroundless", + "define", + "defined", + "detailed", + "detail", + "details", + "render", + "rendered", + "rendering", + "style", + "overexposed", + "quality", + "race", + "shape", + "slay", + "soft_shading", + "suit", + "tale", + "test", + "unit", + "vivid_colors", + "wearing", + "wears", + "melt", + "wood", + "remaining", +} + +PONY_V6_BASE_TAGS = ( + "score_9", + "score_8_up", + "score_7_up", +) + +PONY_V6_POLISH_TAGS = ( + "sharp_focus", +) + +ILLUSTRIOUS_BASE_TAGS = ( + "masterpiece", + "best_quality", +) + +ILLUSTRIOUS_POLISH_TAGS = ( + "anime_illustration", + "clean_linework", + "detailed_subject", +) + +PONY_V6_CONTEXT_EXPANSIONS = ( + (("phoenix",), ("wings", "feathers", "fiery_aura", "mythical_creature")), + (("ogre",), ("large_creature", "muscular_body", "fantasy_monster", "tusks")), + (("tapir",), ("tapir", "animal_focus", "long_snout")), + (("applauding", "clapping"), ("clapping", "hands_together")), + (("sharp",), ("sharp_focus", "crisp_edges")), + (("artificial",), ("artificial_lighting", "synthetic_texture")), + (("black_and_red", "black and red"), ("black_and_red_theme", "high_contrast_colors", "red_accents")), + (("slay", "battle"), ("battle_scene", "action_pose", "dramatic_tension")), + (("cave",), ("cave_interior", "rocky_walls", "dark_atmosphere")), + (("melt", "melting"), ("molten_effects", "dripping_texture", "heat_haze")), + (("beach",), ("ocean", "sand", "shoreline", "sunlight")), + (("indoor", "interior", "room"), ("interior", "ambient_lighting")), + (("close_shot", "close shot", "close_up", "close up"), ("close-up", "upper_body", "subject_focus")), + (("capture_moment", "action_shot", "action shot"), ("action_shot", "motion", "candid_moment")), + (("hidden", "deep"), ("atmospheric_depth", "mysterious_mood")), + (("burnt", "scorched"), ("scorch_marks", "ash", "embers")), + (("alien",), ("alien_artifact", "glowing_object", "sci-fi")), + (("solarpunk",), ("solarpunk", "futuristic_architecture", "warm_light")), + (("church", "temple", "shrine"), ("distant_architecture", "sacred_building")), + (("landscape", "background"), ("wide_background", "environmental_depth")), +) + + +SYSTEM_INSTRUCTIONS = """You are a specialist prompt editor for anime and illustration image generation. +Turn chaotic random vocabulary into strong, coherent English prompts. + +Rules: +- Return only valid JSON with exactly these string keys: + base_prompt, foreground_prompt, background_prompt, negative, report +- Preserve the strongest visual ideas from the source words. +- Curate the input: remove contradictory, weak, duplicate, non-visual, or useless words. +- Never explain outside JSON. +- Keep all image prompts in English. +- Split the positive prompt into global model/style material, foreground subject detail, and background setting detail. For Z-Image, write natural descriptive prose instead of tags. +- Pony v6 and Pony v7 are model names, not subjects. Do not add a pony, horse, or equine character unless the input explicitly asks for one. +- Do not invent copyrighted character names unless they appear in the input or style anchor. +- If the input implies adult content, keep wording as neutral image-generation tags and do not add explicit acts. +- Negative prompts: conservative model-appropriate quality/anatomy/artifact negatives, text/watermark/logo, duplicates, distorted hands, messy composition. For Z-Image, leave negative empty. +- Report: one short sentence naming the retained core idea and what was discarded.""" + + +def _normalize_generate_url(ollama_url): + url = str(ollama_url).strip() or DEFAULT_OLLAMA_URL + if not url.startswith(("http://", "https://")): + url = f"http://{url}" + url = url.rstrip("/") + if url.endswith("/api/generate"): + return url + return f"{url}/api/generate" + + +def _normalize_tags_url(ollama_url): + url = str(ollama_url).strip() or DEFAULT_OLLAMA_URL + if not url.startswith(("http://", "https://")): + url = f"http://{url}" + return f"{url.rstrip('/')}/api/tags" + + +def _available_ollama_models(): + models = [] + request = urllib.request.Request(_normalize_tags_url(DEFAULT_OLLAMA_URL), method="GET") + try: + with urllib.request.urlopen(request, timeout=1) as response: + data = json.loads(response.read().decode("utf-8")) + except (OSError, TimeoutError, urllib.error.URLError, json.JSONDecodeError): + data = {} + + for model in data.get("models", []): + name = str(model.get("name") or model.get("model") or "").strip() + if name and name not in models: + models.append(name) + + if DEFAULT_OLLAMA_MODEL not in models: + models.insert(0, DEFAULT_OLLAMA_MODEL) + return models or [DEFAULT_OLLAMA_MODEL] + + +def _is_reka_flash_model(model): + normalized = str(model).lower() + return "reka-flash" in normalized or "reka_flash" in normalized + + +def _build_reka_prompt(prompt): + return ( + "human: " + + SYSTEM_INSTRUCTIONS + + "\n\nDo not reveal reasoning. Return only the final valid JSON object.\n\n" + + str(prompt).strip() + + " assistant:" + ) + + +def _request_ollama(ollama_url, model, prompt, seed, temperature, top_p, timeout_seconds, context_length=DEFAULT_OLLAMA_CONTEXT_LENGTH): + model_name = str(model).strip() or DEFAULT_OLLAMA_MODEL + is_reka = _is_reka_flash_model(model_name) + options = { + "seed": int(seed), + "temperature": float(temperature), + "top_p": float(top_p), + "num_ctx": _normalize_context_length(context_length), + "num_predict": 1400, + } + if is_reka: + options["stop"] = ["", "<|endoftext|>"] + + payload = { + "model": model_name, + "prompt": _build_reka_prompt(prompt) if is_reka else prompt, + "stream": False, + "format": OUTPUT_SCHEMA, + "options": options, + } + if not is_reka: + payload["system"] = SYSTEM_INSTRUCTIONS + + data = json.dumps(payload).encode("utf-8") + request = urllib.request.Request( + _normalize_generate_url(ollama_url), + data=data, + headers={"Content-Type": "application/json"}, + method="POST", + ) + + try: + with urllib.request.urlopen(request, timeout=max(1, int(timeout_seconds))) as response: + response_data = response.read().decode("utf-8") + except urllib.error.HTTPError as error: + body = error.read().decode("utf-8", errors="replace") + raise RuntimeError(f"Ollama Prompt Refiner: Ollama HTTP {error.code}: {body}") from error + except urllib.error.URLError as error: + raise RuntimeError(f"Ollama Prompt Refiner: could not reach Ollama at {ollama_url}: {error.reason}") from error + except TimeoutError as error: + raise RuntimeError(f"Ollama Prompt Refiner: Ollama request timed out after {timeout_seconds} seconds") from error + + try: + envelope = json.loads(response_data) + except json.JSONDecodeError as error: + raise RuntimeError(f"Ollama Prompt Refiner: invalid Ollama response envelope: {error}") from error + + if not envelope.get("done", False): + raise RuntimeError("Ollama Prompt Refiner: Ollama response did not finish cleanly") + + return str(envelope.get("response", "")).strip() + + +def _extract_json_object(text): + text = str(text).strip() + if not text: + raise ValueError("empty response") + + try: + return json.loads(text) + except json.JSONDecodeError: + pass + + start = text.find("{") + end = text.rfind("}") + if start == -1 or end == -1 or end <= start: + raise ValueError("response does not contain a JSON object") + + try: + return json.loads(text[start : end + 1]) + except json.JSONDecodeError as error: + raise ValueError(str(error)) from error + + +def _validate_result(data): + if not isinstance(data, dict): + raise ValueError("JSON root is not an object") + + if all(key in data for key in RESPONSE_KEYS): + keys = RESPONSE_KEYS + elif all(key in data for key in LEGACY_RESPONSE_KEYS): + keys = LEGACY_RESPONSE_KEYS + else: + expected = " or ".join(("{base_prompt, foreground_prompt, background_prompt, negative, report}", "{positive, negative, report}")) + available = ", ".join(sorted(str(key) for key in data.keys())) + raise ValueError(f"missing result keys; expected {expected}; available: {available}") + + values = {key: str(data[key]).strip() for key in keys} + empty = [key for key, value in values.items() if not value and key != "negative"] + if empty: + raise ValueError(f"empty keys: {', '.join(empty)}") + + return values + + +def _normalize_style_cluster(style_cluster): + try: + value = int(style_cluster) + except (TypeError, ValueError): + value = DEFAULT_STYLE_CLUSTER + return max(0, min(2048, value)) + + +def _normalize_context_length(context_length): + try: + value = int(context_length) + except (TypeError, ValueError): + value = DEFAULT_OLLAMA_CONTEXT_LENGTH + return max(512, min(262144, value)) + + +def _normalize_target_profile(target_profile): + profile = str(target_profile).strip().lower() + if profile in TARGET_PROFILES: + return profile + return DEFAULT_TARGET_PROFILE + + +def _is_model_meta_term(normalized): + compact = re.sub(r"[^a-z0-9]+", "_", str(normalized).lower()).strip("_") + if re.fullmatch(r"pony_v?\d+", compact): + return True + if re.fullmatch(r"(character|subject)_v?\d+(?:_sdxl)?", compact): + return True + if compact in {"character_sdxl", "subject_sdxl"}: + return True + return compact in { + "pony_6", + "pony_7", + "pony_v6", + "pony_v7", + "character_6", + "character_7", + "character_v6", + "character_v7", + "character_6_sdxl", + "character_7_sdxl", + "subject_6", + "subject_7", + "subject_v6", + "subject_v7", + } + + +def _is_low_value_prompt_term(normalized): + compact = re.sub(r"[^a-z0-9]+", "_", str(normalized).lower()).strip("_") + return compact in LOW_VALUE_PROMPT_TERMS + + +def _source_allows_equine(word_salad, style_anchor): + return bool(EQUINE_PATTERN.search(f"{word_salad} {style_anchor}")) + + +def _remove_model_name_leaks(value): + value = MODEL_LABEL_PATTERN.sub("character", value) + value = MODEL_VERSION_LEAK_PATTERN.sub(lambda match: match.group(1), value) + replacements = { + "pony": "character", + "ponies": "characters", + "horse": "character", + "horses": "characters", + "equine": "character", + "stallion": "character", + "mare": "character", + "foal": "character", + } + + def replace(match): + text = match.group(0) + replacement = replacements[text.lower()] + if text[:1].isupper(): + return replacement.capitalize() + return replacement + + return EQUINE_PATTERN.sub(replace, value) + + +def _has_negative_markers(value): + lowered = value.lower() + return any(marker in lowered for marker in NEGATIVE_MARKERS) + + +def _with_negative_baseline(target_profile, value): + baseline_terms = NEGATIVE_BASELINES.get(_normalize_target_profile(target_profile)) + if not baseline_terms: + return value + + if not _has_negative_markers(value): + return ", ".join(baseline_terms) + + result = value + lowered = result.lower() + for term in baseline_terms: + if term.lower() not in lowered: + result = f"{result}, {term}" + lowered = result.lower() + return result + + +def _strip_commas_for_profile(target_profile, value): + if _normalize_target_profile(target_profile) in ("pony_v7", "z_image"): + return value + return re.sub(r"\s+", " ", str(value).replace(",", " ")).strip() + + +def _clean_report(report, target_profile, word_salad, style_anchor): + report = re.sub(r"\s+", " ", str(report).replace(",", " ")).strip() + lowered = report.lower() + if lowered.startswith("built one ") or "parser detail:" in lowered: + return report + + terms = _curated_terms(f"{style_anchor} {word_salad}", limit=8) + core = " ".join(_prompt_token(term) for term in terms[:5]) if terms else "curated visual terms" + profile_label = { + "pony_v6": "Pony v6", + "illustrious": "Illustrious", + "pony_v7": "Pony v7", + "z_image": "Z-Image", + }.get(_normalize_target_profile(target_profile), "selected profile") + return f"Built one {profile_label} prompt from {core} and removed weak filler terms" + + +def _postprocess_result(values, target_profile, word_salad, style_anchor, style_cluster=DEFAULT_STYLE_CLUSTER): + target_profile = _normalize_target_profile(target_profile) + allow_equine = _source_allows_equine(word_salad, style_anchor) + style_cluster = _normalize_style_cluster(style_cluster) + if isinstance(values, dict): + data = {key: str(value).strip() for key, value in values.items()} + else: + data = {key: value for key, value in zip(LEGACY_RESPONSE_KEYS, values)} + + positive = str(data.get("positive", "")).strip() + base_prompt = str(data.get("base_prompt", "")).strip() + foreground_prompt = str(data.get("foreground_prompt", "")).strip() + background_prompt = str(data.get("background_prompt", "")).strip() + if not positive: + positive = _join_positive_parts(target_profile, base_prompt, foreground_prompt, background_prompt) + negative = str(data["negative"]).strip() + report = str(data["report"]).strip() + + if not allow_equine: + positive = _remove_model_name_leaks(positive) + base_prompt = _remove_model_name_leaks(base_prompt) + foreground_prompt = _remove_model_name_leaks(foreground_prompt) + background_prompt = _remove_model_name_leaks(background_prompt) + negative = _remove_model_name_leaks(negative) + report = _remove_model_name_leaks(report) + + negative = _with_negative_baseline(target_profile, negative) + if target_profile == "z_image": + provided = { + "base_prompt": base_prompt, + "foreground_prompt": foreground_prompt, + "background_prompt": background_prompt, + } + base_prompt, foreground_prompt, background_prompt = _z_image_prompt_parts( + positive, + word_salad, + style_anchor, + provided if any(provided.values()) else None, + ) + positive = _join_positive_parts(target_profile, base_prompt, foreground_prompt, background_prompt) + negative = "" + elif target_profile in ("pony_v6", "illustrious"): + provided = { + "base_prompt": base_prompt, + "foreground_prompt": foreground_prompt, + "background_prompt": background_prompt, + } + base_prompt, foreground_prompt, background_prompt = _balanced_tag_prompt_parts( + target_profile, + positive, + word_salad, + style_anchor, + provided if any(provided.values()) else None, + ) + positive = _join_positive_parts(target_profile, base_prompt, foreground_prompt, background_prompt) + else: + base_prompt, foreground_prompt, background_prompt = _structured_pony_v7_parts( + positive, + word_salad, + style_anchor, + style_cluster, + ) + positive = _join_positive_parts(target_profile, base_prompt, foreground_prompt, background_prompt) + + base_prompt = _strip_commas_for_profile(target_profile, base_prompt) + foreground_prompt = _strip_commas_for_profile(target_profile, foreground_prompt) + background_prompt = _strip_commas_for_profile(target_profile, background_prompt) + if target_profile not in ("pony_v7", "z_image"): + positive = _join_positive_parts(target_profile, base_prompt, foreground_prompt, background_prompt) + positive = _strip_commas_for_profile(target_profile, positive) + negative = _strip_commas_for_profile(target_profile, negative) + report = _clean_report(report, target_profile, word_salad, style_anchor) + + return (positive, negative, report, base_prompt, foreground_prompt, background_prompt) + + +def _curated_terms(text, limit=28, filter_low_value=True): + terms = [] + seen = set() + for raw_term in re.split(r"[\s,;|]+", str(text).strip()): + term = raw_term.strip(" .:()[]{}\"'") + if not term: + continue + normalized = term.lower() + if ( + normalized in STOPWORDS + or normalized in seen + or _is_model_meta_term(normalized) + or (filter_low_value and _is_low_value_prompt_term(normalized)) + or normalized.isdigit() + or not re.search(r"[a-zA-Z0-9]", normalized) + or normalized.startswith(("score_", "rating_", "style_cluster_")) + or re.fullmatch(r"tag\d+", normalized) + ): + continue + seen.add(normalized) + terms.append(term.replace("_", " ")) + if len(terms) >= limit: + break + return terms + + +def _prompt_token(value): + token = re.sub(r"[^a-zA-Z0-9_<>,.:+-]+", "_", str(value).strip().lower()) + token = re.sub(r"_+", "_", token).strip("_") + token = re.sub(r"^source_", "", token) + return token + + +def _dedupe_terms(terms): + result = [] + seen = set() + for term in terms: + clean = str(term).strip() + if not clean: + continue + normalized = re.sub(r"\s+", "_", clean.lower()) + if normalized in seen: + continue + seen.add(normalized) + result.append(clean) + return result + + +def _contextual_pony_v6_tags(terms): + joined = " ".join(terms).lower() + result = [] + for triggers, expansions in PONY_V6_CONTEXT_EXPANSIONS: + if any(trigger in joined for trigger in triggers): + result.extend(expansions) + return result + + +def _pony_v6_stopword_ratio(value): + words = re.findall(r"\b[a-zA-Z]+\b", str(value).lower()) + if not words: + return 0.0 + return sum(1 for word in words if word in STOPWORDS) / len(words) + + +def _needs_pony_v6_refine(prompt): + prompt = str(prompt) + lowered = prompt.lower() + has_bad_terms = any(_is_model_meta_term(term) or _is_low_value_prompt_term(term) for term in re.split(r"[\s,;|]+", lowered)) + return "," not in prompt or _word_count(prompt) < 18 or _pony_v6_stopword_ratio(prompt) > 0.18 or has_bad_terms + + +def _source_content_tags(word_salad, value, style_anchor, limit=12): + anchor_terms = {_prompt_token(term) for term in _curated_terms(style_anchor, limit=48)} + source_terms = _curated_terms(word_salad, limit=28, filter_low_value=False) + if not source_terms: + source_terms = _curated_terms(value, limit=28, filter_low_value=False) + content_tags = [_prompt_token(term) for term in source_terms] + return [ + tag + for tag in content_tags + if tag + and tag not in STOPWORDS + and tag not in anchor_terms + and not _is_model_meta_term(tag) + and not _is_low_value_prompt_term(tag) + ][:limit], source_terms + + +def _refined_pony_v6_prompt(value, word_salad, style_anchor): + if not _needs_pony_v6_refine(value): + return value + + anchor = str(style_anchor).strip() + content_tags, source_terms = _source_content_tags(word_salad, value, anchor, limit=10) + expansion_tags = [_prompt_token(term) for term in _contextual_pony_v6_tags(source_terms)] + + parts = list(PONY_V6_BASE_TAGS) + if anchor: + parts.append(anchor) + parts.extend(content_tags[:12]) + parts.extend(expansion_tags) + parts.extend(PONY_V6_POLISH_TAGS) + return ", ".join(_dedupe_terms(parts)[:32]) + + +def _needs_illustrious_refine(prompt): + prompt = str(prompt) + lowered = prompt.lower() + has_bad_terms = any(_is_model_meta_term(term) or _is_low_value_prompt_term(term) for term in re.split(r"[\s,;|]+", lowered)) + return _word_count(prompt) < 10 or _pony_v6_stopword_ratio(prompt) > 0.14 or has_bad_terms + + +def _refined_illustrious_prompt(value, word_salad, style_anchor): + if not _needs_illustrious_refine(value): + return value + + anchor = str(style_anchor).strip() + content_tags, source_terms = _source_content_tags(word_salad, value, anchor, limit=12) + expansion_tags = [_prompt_token(term) for term in _contextual_pony_v6_tags(source_terms)] + + parts = list(ILLUSTRIOUS_BASE_TAGS) + if anchor: + parts.append(anchor) + parts.extend(content_tags) + parts.extend(expansion_tags[:10]) + parts.extend(ILLUSTRIOUS_POLISH_TAGS) + return ", ".join(_dedupe_terms(parts)[:30]) + + +def _tags_from_terms(terms, limit=18): + tags = [] + for term in terms: + tag = _prompt_token(term) + if tag and tag not in STOPWORDS and not _is_model_meta_term(tag) and not _is_low_value_prompt_term(tag): + tags.append(tag) + return _dedupe_terms(tags)[:limit] + + +FOREGROUND_TAG_FILLERS = ( + "clear_subject", + "readable_pose", + "focused_expression", + "visible_silhouette", + "defined_anatomy", + "material_texture", + "outfit_detail", + "surface_highlights", + "gesture_focus", + "foreground_presence", + "sharp_focal_point", + "character_detail", + "body_orientation", + "layered_shapes", + "clean_edges", + "intentional_action", + "visible_props", + "balanced_subject", + "pose_readability", + "fine_details", + "subject_focus", + "expressive_design", + "controlled_motion", + "clear_contact_points", +) + +BACKGROUND_TAG_FILLERS = ( + "detailed_background", + "environmental_depth", + "middle_ground", + "distant_background", + "atmospheric_haze", + "visible_setting", + "spatial_layering", + "background_props", + "ambient_lighting", + "environmental_color", + "distant_objects", + "setting_detail", + "ground_surface", + "far_horizon", + "surrounding_space", + "depth_cues", + "scene_context", + "background_texture", + "local_atmosphere", + "scale_variation", + "soft_distance", + "clear_location", + "supporting_scenery", + "placed_environment", + "coastline_detail", + "pathway_detail", + "noir_atmosphere", + "safety_equipment", + "travel_marker", + "surface_detail", + "scene_anchor", + "local_landmark", + "weathered_material", + "directional_light", + "environmental_shadow", + "layered_depth", + "setting_context", + "background_anchor", + "distant_structure", + "ambient_detail", +) + +FOREGROUND_EXPANSIONS = ( + (("latex",), ("glossy_latex", "reflective_material", "tight_surface", "specular_highlights")), + (("lion",), ("lion_focus", "mane_detail", "fur_texture", "animal_features", "claws")), + (("blurring", "blur"), ("motion_blur", "soft_edges", "speed_effect", "movement_trail")), + (("blended",), ("blended_colors", "merged_shapes", "soft_transition", "layered_forms")), + (("blobby",), ("rounded_shapes", "soft_volume", "organic_form", "bulging_surface")), + (("break_apart", "break apart"), ("fragmented_motion", "separating_pieces", "impact_detail", "scattered_fragments")), + (("notice", "listen_to", "listen to"), ("alert_expression", "attentive_pose", "turned_head", "listening_focus")), + (("group_shot", "group shot"), ("multiple_subjects", "group_composition", "shared_focus", "subject_spacing")), + (("jigsaw",), ("puzzle_piece", "interlocking_shapes", "cut_edges", "fragment_pattern")), +) + +BACKGROUND_EXPANSIONS = ( + (("coast",), ("coastal_setting", "shoreline_detail", "distant_water", "windy_atmosphere")), + (("mountain",), ("mountain_background", "rocky_slope", "distant_peak", "highland_landscape", "open_sky")), + (("iraq",), ("arid_landscape", "sun_bleached_stone", "dusty_air", "distant_architecture", "desert_light")), + (("sepia",), ("sepia_atmosphere", "warm_tint", "aged_color", "muted_contrast")), + (("luggage_tag", "luggage tag"), ("travel_tag", "suitcase_detail", "paper_label", "travel_prop")), + (("lens_cap", "lens cap"), ("camera_accessory", "small_plastic_cap", "photography_prop", "nearby_gear")), + (("jigsaw",), ("scattered_puzzle_pieces", "tabletop_detail", "interlocking_pattern", "background_puzzle")), + (("group_shot", "group shot"), ("wide_scene_space", "shared_environment", "crowd_spacing", "scene_depth")), +) + + +def _tokenize_prompt_tags(value, limit=64): + tags = [] + for raw_term in re.split(r"[\s,;|]+", str(value).strip()): + raw_clean = raw_term.strip() + tag = _prompt_token(raw_clean) + if raw_clean.lower().startswith("source_") and tag and not tag.startswith("source_"): + tag = f"source_{tag}" + if ( + tag + and tag not in STOPWORDS + and not _is_model_meta_term(tag) + and not re.fullmatch(r"tag\d+", tag) + ): + tags.append(tag) + return _dedupe_terms(tags)[:limit] + + +def _expand_tags_from_terms(terms, expansions): + joined = " ".join(str(term).lower() for term in terms) + tags = [] + for triggers, expanded_tags in expansions: + if any(trigger in joined for trigger in triggers): + tags.extend(expanded_tags) + return tags + + +def _normalize_split_tag(tag): + raw = str(tag).strip() + normalized = _prompt_token(raw) + if raw.lower().startswith("source_") and normalized and not normalized.startswith("source_"): + normalized = f"source_{normalized}" + return normalized + + +def _is_protected_split_tag(tag): + tag = str(tag).strip().lower() + return tag.startswith(("score_", "rating_", "source_", "style_cluster_")) + + +def _split_tag_display(tag): + tag = str(tag).strip() + if _is_protected_split_tag(tag): + return tag + return tag.replace("_", " ") + + +def _split_tag_key(tag): + display = _split_tag_display(tag).lower() + return re.sub(r"[^a-z0-9]+", " ", display).strip() + + +def _dedupe_split_tags(tags): + result = [] + seen = set() + for tag in tags: + normalized = _normalize_split_tag(tag) + if not normalized: + continue + key = _split_tag_key(normalized) + if not key or key in seen: + continue + seen.add(key) + result.append(normalized) + return result + + +def _drop_redundant_split_words(tags): + tags = list(tags) + compound_words = set() + for tag in tags: + if _is_protected_split_tag(tag) or "_" not in str(tag): + continue + parts = [part for part in str(tag).lower().split("_") if part] + if len(parts) >= 2: + compound_words.update(parts) + + result = [] + for tag in tags: + if _is_protected_split_tag(tag): + result.append(tag) + continue + key = _split_tag_key(tag) + if " " not in key and key in compound_words: + continue + result.append(tag) + return result + + +def _split_tags_text(tags): + return " ".join(_split_tag_display(tag) for tag in tags).replace(",", " ").strip() + + +def _split_base_concepts(base_tags): + concepts = set() + for tag in base_tags: + normalized = _normalize_split_tag(tag) + key = _split_tag_key(normalized) + if key: + concepts.add(key) + if normalized.startswith("source_"): + concepts.add(_split_tag_key(normalized.removeprefix("source_"))) + if normalized.startswith("rating_"): + concepts.add(_split_tag_key(normalized.removeprefix("rating_"))) + return concepts + + +def _remove_base_concept_tags(tags, base_concepts): + result = [] + for tag in tags: + normalized = _normalize_split_tag(tag) + if not normalized: + continue + if _split_tag_key(normalized) in base_concepts: + continue + result.append(tag) + return result + + +def _fit_tag_words(tags, min_words, max_words, fillers): + result = _drop_redundant_split_words(_dedupe_split_tags(tags)) + filler_index = 0 + while _word_count(_split_tags_text(result)) < min_words and filler_index < len(fillers): + result.append(fillers[filler_index]) + result = _drop_redundant_split_words(_dedupe_split_tags(result)) + filler_index += 1 + + while _word_count(_split_tags_text(result)) > max_words and len(result) > 1: + result.pop() + return _split_tags_text(result) + + +def _balanced_tag_prompt_parts(target_profile, value, word_salad, style_anchor, provided=None): + target_profile = _normalize_target_profile(target_profile) + provided = provided or {} + combined_source = " ".join( + str(part) + for part in ( + word_salad, + value, + provided.get("foreground_prompt", ""), + provided.get("background_prompt", ""), + ) + ) + source_terms = _curated_terms(combined_source, limit=48, filter_low_value=False) + foreground_terms, background_terms, style_terms, all_terms = _split_pony_v7_terms(word_salad, "", combined_source) + anchor_tags = _tokenize_prompt_tags(style_anchor, limit=24) + provided_base_tags = _tokenize_prompt_tags(provided.get("base_prompt", ""), limit=24) + provided_foreground_tags = _tokenize_prompt_tags(provided.get("foreground_prompt", ""), limit=80) + provided_background_tags = _tokenize_prompt_tags(provided.get("background_prompt", ""), limit=80) + + if target_profile == "pony_v6": + base_tags = list(PONY_V6_BASE_TAGS) + base_tags.extend(anchor_tags) + base_tags.extend(provided_base_tags) + base_tags.extend(("anime_illustration", "sharp_focus", "clean_linework", "best_quality")) + else: + base_tags = list(ILLUSTRIOUS_BASE_TAGS) + base_tags.extend(anchor_tags) + base_tags.extend(provided_base_tags) + base_tags.extend(("amazing_quality", "very_aesthetic", "polished_illustration", "soft_shading", "sharp_linework")) + + base_prompt = _fit_tag_words( + base_tags, + SPLIT_BASE_WORD_RANGE[0], + SPLIT_BASE_WORD_RANGE[1], + ("anime_artwork", "detailed_style", "polished_rendering", "clean_focus"), + ) + base_concepts = _split_base_concepts(_tokenize_prompt_tags(base_prompt, limit=40)) + + source_tags = _tags_from_terms(source_terms, limit=48) + foreground_tags = [] + foreground_tags.extend(provided_foreground_tags) + foreground_tags.extend(_tags_from_terms(foreground_terms, limit=36)) + foreground_tags.extend(source_tags) + foreground_tags.extend(_expand_tags_from_terms(source_terms, FOREGROUND_EXPANSIONS)) + foreground_tags.extend(_contextual_pony_v6_tags(source_terms)) + foreground_tags = _remove_base_concept_tags(foreground_tags, base_concepts) + foreground_prompt = _fit_tag_words( + foreground_tags, + SPLIT_DETAIL_WORD_RANGE[0], + SPLIT_DETAIL_WORD_RANGE[1], + FOREGROUND_TAG_FILLERS, + ) + + extracted_background = _extract_pony_v7_background_phrase(value) + background_tags = [] + background_tags.extend(provided_background_tags) + background_tags.extend(_tags_from_terms(background_terms, limit=36)) + background_tags.extend(_tokenize_prompt_tags(extracted_background, limit=24)) + background_tags.extend(_expand_tags_from_terms(source_terms, BACKGROUND_EXPANSIONS)) + background_tags = _remove_base_concept_tags(background_tags, base_concepts) + background_prompt = _fit_tag_words( + background_tags, + SPLIT_DETAIL_WORD_RANGE[0], + SPLIT_DETAIL_WORD_RANGE[1], + BACKGROUND_TAG_FILLERS, + ) + + return base_prompt, foreground_prompt, background_prompt + + +def _has_generic_background_prompt(value): + lowered = str(value).lower() + return any( + marker in lowered + for marker in ( + "coherent environment with visible depth", + "supporting background elements", + "background supports the subject", + "clear spatial layering and does not overpower", + ) + ) + + +def _illustrious_prompt_parts(value, word_salad, style_anchor, provided=None): + provided = provided or {} + anchor = str(style_anchor).strip() + foreground_terms, background_terms, style_terms, all_terms = _split_pony_v7_terms(word_salad, style_anchor, value) + + base_parts = list(ILLUSTRIOUS_BASE_TAGS) + provided_base = str(provided.get("base_prompt", "")).strip() + if provided_base: + base_parts.extend(part.strip() for part in provided_base.split(",") if part.strip()) + if anchor: + base_parts.append(anchor) + base_parts.extend(_tags_from_terms(style_terms[:6], limit=6)) + base_parts.extend(ILLUSTRIOUS_POLISH_TAGS) + base_prompt = ", ".join(_dedupe_terms(base_parts)[:24]) + + foreground_prompt = str(provided.get("foreground_prompt", "")).strip() + if not foreground_prompt or _word_count(foreground_prompt) < 3: + foreground_tags = _tags_from_terms(foreground_terms[:18], limit=18) + if not foreground_tags: + foreground_tags = _tags_from_terms(all_terms[:14], limit=14) + foreground_prompt = ", ".join(foreground_tags or ["detailed_subject", "clear_pose", "focused_character"]) + + background_prompt = str(provided.get("background_prompt", "")).strip() + if not background_prompt or _word_count(background_prompt) < 3 or _has_generic_background_prompt(background_prompt): + background_tags = _tags_from_terms(background_terms[:14], limit=14) + if not background_tags: + extracted = _extract_pony_v7_background_phrase(value) + background_tags = _tags_from_terms(re.split(r"[\s,;|]+", extracted), limit=12) + background_prompt = ", ".join(background_tags or ["detailed_background", "atmospheric_background", "interior_details"]) + + return base_prompt, foreground_prompt, background_prompt + + +def _word_count(text): + return len(re.findall(r"\b\w+\b", str(text))) + + +def _sentence_from_terms(terms, fallback): + terms = [term for term in terms if term] + if not terms: + return fallback + if len(terms) == 1: + return terms[0] + if len(terms) == 2: + return f"{terms[0]} and {terms[1]}" + return f"{', '.join(terms[:-1])}, and {terms[-1]}" + + +def _clean_unstructured_sentence(value): + value = re.sub(r"[ \t]+", " ", str(value).strip()) + if not value: + return "" + + value = _strip_pony_v7_control_terms(value) + foreground_match = re.search(r"1\.\s*foreground:\s*(.+?)(?:\s*2\.\s*background:|\s*#\s*stylistic description:|$)", value, re.IGNORECASE) + if foreground_match: + first_line = foreground_match.group(1).strip() + else: + lines = [ + line.strip() + for line in str(value).splitlines() + if line.strip() + and not line.strip().lower().startswith(("score_", "#", "2. background", "# factual", "# stylistic", "# danbooru")) + and not _looks_like_tag_list(line.strip()) + and not _looks_like_pony_v7_base_meta(line.strip()) + and not _looks_like_pony_v7_background_paragraph(line.strip()) + and not _looks_like_pony_v7_style_paragraph(line.strip()) + ] + first_line = lines[0] if lines else "" + first_line = re.sub(r"^#+\s*[^:]+:\s*", "", first_line) + first_line = re.sub(r"\s+", " ", first_line) + first_line = _strip_leading_pony_v7_tag_preamble(first_line) + first_line = _strip_pony_v7_field_label(first_line) + first_sentence = re.split(r"(?<=[.!?])\s+", first_line)[0].strip() + first_sentence = first_sentence.strip(" .") + if not first_sentence: + return "" + first_sentence = first_sentence[:1].upper() + first_sentence[1:] + return f"{first_sentence}." + + +def _strip_pony_v7_control_terms(value): + value = re.sub( + r"\b(?:score_\d+(?:_up)?|rating_[a-z0-9_]+|style_cluster_\d+)\b\s*,?\s*", + "", + str(value), + flags=re.IGNORECASE, + ) + value = re.sub(r"[ \t]+([,.])", r"\1", value) + value = re.sub(r"(?:^|\n)\s*,+\s*", "\n", value) + value = re.sub(r",\s*,+", ", ", value) + return value.strip(" ,") + + +PONY_V7_FIELD_LABEL_PATTERN = re.compile( + r"^\s*(?:" + r"global\s+(?:model/)?source/style\s+tags?" + r"|global\s+model\s+source\s+style\s+tags?" + r"|base\s+prompt" + r"|foreground\s+prompt" + r"|foreground\s+description" + r"|background\s+prompt" + r"|background\s+description" + r"|style\s+tags?" + r"|danbooru\s+tags?" + r")\s*[:\-–—,]*\s*", + re.IGNORECASE, +) + + +def _strip_pony_v7_field_label(value): + previous = str(value).strip() + while True: + current = PONY_V7_FIELD_LABEL_PATTERN.sub("", previous).strip() + if current == previous: + return current + previous = current + + +def _looks_like_pony_v7_base_meta(value): + lowered = str(value).strip().lower() + return bool(PONY_V7_FIELD_LABEL_PATTERN.match(lowered)) or any( + marker in lowered + for marker in ( + "global model/source/style tags", + "global model source style tags", + "source/style tags", + "model/source/style", + ) + ) + + +def _looks_like_pony_v7_background_paragraph(value): + lowered = str(value).strip().lower() + return lowered.startswith("behind the subject") or "background" in lowered + + +def _looks_like_pony_v7_style_paragraph(value): + lowered = str(value).strip().lower() + return any( + marker in lowered + for marker in ( + "digital anime illustration", + "clean linework", + "soft shading", + "camera framing", + "focused color palette", + "medium shot", + "balanced composition", + "low angle perspective", + ) + ) + + +def _looks_like_pony_v7_prefix_tag(value): + normalized = re.sub(r"[_-]+", " ", str(value).strip().lower()) + normalized = re.sub(r"\s+", " ", normalized).strip(" .") + if not normalized: + return True + if normalized in { + "masterpiece", + "best quality", + "high quality", + "source anthro", + "source furry", + "source anime", + "source cartoon", + "anthro", + "furry", + "explicit", + "solo", + }: + return True + sentence_markers = { + "a", + "an", + "the", + "this", + "that", + "wearing", + "holding", + "standing", + "sitting", + "lying", + "walking", + "running", + "with", + "in", + "on", + "near", + "behind", + } + words = normalized.split() + return len(words) <= 3 and not any(word in sentence_markers for word in words) + + +def _strip_leading_pony_v7_tag_preamble(value): + parts = [part.strip() for part in str(value).split(",")] + while len(parts) > 1 and _looks_like_pony_v7_prefix_tag(parts[0]): + parts.pop(0) + return ", ".join(part for part in parts if part).strip() + + +PONY_V7_BACKGROUND_KEYWORDS = ( + "background", + "barren", + "beach", + "building", + "castle", + "church", + "city", + "coast", + "desert", + "decor", + "desk", + "dock", + "forest", + "garden", + "harbor", + "harbour", + "interior", + "landscape", + "microphone", + "mountain", + "ocean", + "pier", + "plushie", + "plushies", + "port", + "poster", + "posters", + "room", + "ruined", + "sail", + "sea", + "ship", + "shore", + "shrine", + "sky", + "street", + "temple", + "valley", + "wall", + "water", +) + + +def _clean_pony_v7_background_phrase(value): + phrase = _strip_pony_v7_control_terms(value) + phrase = re.sub(r"^\s*behind\s+(?:the\s+)?subject,?\s*(?:the\s+)?scene\s+includes\s+", "", phrase, flags=re.IGNORECASE) + phrase = re.sub(r"^\s*(?:the\s+)?scene\s+includes\s+", "", phrase, flags=re.IGNORECASE) + phrase = re.sub(r"\b(?:in|on|at|against|inside|within|under|beneath|behind)\s+the\s+background\b", "", phrase, flags=re.IGNORECASE) + phrase = re.sub(r"\bthe\s+background\b", "", phrase, flags=re.IGNORECASE) + phrase = re.sub(r"\s+", " ", phrase).strip(" ,.;") + phrase = _strip_leading_pony_v7_tag_preamble(phrase) + if not phrase or _word_count(phrase) < 2: + return "" + return phrase + + +def _extract_pony_v7_background_phrase(value): + cleaned = _strip_pony_v7_control_terms(value) + sentences = re.split(r"(?<=[.!?])\s+", cleaned) + for sentence in sentences: + if re.search(r"\b(?:these setting details|background should contain|background supports|spatial layering)\b", sentence, re.IGNORECASE): + continue + if not re.search(r"\b(?:background|behind|harbou?r|dock|pier|port|ship|sail|sea|ocean|street|room|forest|city|sky)\b", sentence, re.IGNORECASE): + continue + patterns = ( + r"\b(?:in|inside|within|at|on|against|under|beneath)\s+((?:a|an|the)\s+[^.!?]{5,160})", + r"\bbehind\s+(?:the\s+subject,?\s*)?((?:a|an|the)?\s*[^.!?]{5,160})", + r"\bwith\s+([^.!?]{5,120}\bbackground\b[^.!?]{0,60})", + ) + for pattern in patterns: + match = re.search(pattern, sentence, re.IGNORECASE) + if match: + phrase = _clean_pony_v7_background_phrase(match.group(1)) + if phrase: + return phrase + phrase = _clean_pony_v7_background_phrase(sentence) + if phrase and _word_count(phrase) <= 24: + return phrase + return "" + + +def _pony_v7_background_description(value, background_terms): + extracted = _extract_pony_v7_background_phrase(value) + if extracted: + return extracted + return _sentence_from_terms( + background_terms[:7], + "a specific visible setting with identifiable structures, surfaces, light sources, and distant context", + ) + + +PONY_V7_FOREGROUND_META_TERMS = { + "anthro", + "furry", + "explicit", + "rating_explicit", + "source_anthro", + "source_furry", + "source_anime", + "source_cartoon", + "masterpiece", + "best_quality", + "amazing_quality", + "very_aesthetic", +} + + +def _pony_v7_foreground_terms(terms): + result = [] + for term in terms: + token = _prompt_token(term) + if token in PONY_V7_FOREGROUND_META_TERMS: + continue + result.append(term) + return result + + +def _term_matches_keyword(term, keyword): + normalized = re.sub(r"[_-]+", " ", str(term).lower()) + normalized = re.sub(r"\s+", " ", normalized).strip() + keyword = str(keyword).lower().strip() + if " " in keyword: + return keyword in normalized + return keyword in normalized.split() + + +def _split_pony_v7_terms(word_salad, style_anchor, existing_prompt): + source_terms = _curated_terms(f"{style_anchor} {word_salad} {existing_prompt}", limit=36) + style_keywords = ( + "angle", + "bokeh", + "cinematic", + "close up", + "composition", + "depth", + "illustration", + "lighting", + "medium shot", + "perspective", + "shading", + "soft", + "volumetric", + ) + + foreground = [] + background = [] + style = [] + for term in source_terms: + normalized = term.lower() + if any(_term_matches_keyword(normalized, keyword) for keyword in style_keywords): + style.append(term) + elif any(_term_matches_keyword(normalized, keyword) for keyword in PONY_V7_BACKGROUND_KEYWORDS): + background.append(term) + else: + foreground.append(term) + + return foreground[:12], background[:8], style[:6], source_terms + + +Z_IMAGE_FORBIDDEN_TAG_PATTERN = re.compile( + r"\b(?:" + r"score_\d+(?:_up)?|rating_[a-z0-9_]+|style_cluster_\d+|source[_ ][a-z0-9_]+|" + r"masterpiece|best|quality|best[_ ]quality|amazing|amazing[_ ]quality|very[_ ]aesthetic|aesthetic|" + r"worst[_ ]quality|low[_ ]quality|danbooru|8k|tag\d+" + r")\b,?\s*", + re.IGNORECASE, +) + + +def _strip_z_image_labels(value): + value = re.sub( + r"(?im)^\s*(?:base_prompt|foreground_prompt|background_prompt|positive|negative|report|" + r"main subject|environment|lighting|camera|style|color mood|additional details)\s*[:\-–—]\s*", + "", + str(value), + ) + return value + + +def _clean_z_image_text(value): + text = _strip_z_image_labels(value) + text = Z_IMAGE_FORBIDDEN_TAG_PATTERN.sub("", text) + text = re.sub(r"[_]+", " ", text) + text = re.sub(r"\s+,", ",", text) + text = re.sub(r",\s*,+", ", ", text) + text = re.sub(r"\s+", " ", text).strip(" ,.;") + if not text: + return "" + return text[:1].upper() + text[1:] + + +def _z_image_terms(word_salad, style_anchor, value, limit=18): + source = f"{style_anchor} {word_salad}" + terms = _curated_terms(source, limit=limit, filter_low_value=False) + if len(terms) < 4: + terms = _curated_terms(f"{source} {value}", limit=limit, filter_low_value=False) + result = [] + seen = set() + for term in terms: + clean = _clean_z_image_text(term) + key = clean.lower() + if not clean or key in seen or Z_IMAGE_FORBIDDEN_TAG_PATTERN.fullmatch(key): + continue + seen.add(key) + result.append(clean) + return result + + +def _z_image_section_is_usable(value, min_words=18): + text = _clean_z_image_text(value) + if _word_count(text) < min_words: + return False + comma_count = text.count(",") + sentence_count = len([part for part in re.split(r"(?<=[.!?])\s+", text) if part.strip()]) + return sentence_count >= 1 and comma_count <= max(8, _word_count(text) // 8) + + +def _z_image_foreground_fallback(word_salad, style_anchor, value): + terms = _z_image_terms(word_salad, style_anchor, value, limit=12) + subject = _sentence_from_terms(terms[:8], "a clearly defined subject based on the provided idea") + return ( + f"The image centers on {subject}. The subject appears as a real visual presence with a readable pose, " + "clear body language, visible clothing or surface texture, and small details that make the concept feel " + "intentional. The action is easy to understand at a glance, with natural proportions, believable materials, " + "and a few distinctive features that guide the viewer's attention. The prompt should describe what the viewer " + "can actually see: the angle of the head or object, the placement of the hands or edges, the weight of the " + "body, the condition of fabric, metal, skin, fur, glass, dust, water, or other visible surfaces. Give the " + "foreground enough physical specificity that the subject could be painted without guessing the silhouette. " + "Include small but useful cues such as tension in the posture, direction of the gaze, contact with nearby " + "props, reflected highlights, worn seams, scratches, damp areas, loose strands, or other local details that " + "make the main figure or object feel present inside the scene." + ) + + +def _z_image_background_fallback(word_salad, style_anchor, value): + _, background_terms, _, all_terms = _split_pony_v7_terms(word_salad, style_anchor, "") + background = _sentence_from_terms( + _z_image_terms(" ".join(background_terms), "", "", limit=8) or _z_image_terms(" ".join(all_terms), "", "", limit=6), + "a concrete environment with visible depth and recognizable surroundings", + ) + return ( + f"The scene takes place in {background}. The environment supports the subject with a clear foreground, " + "middle ground, and distant background, using recognizable surfaces, objects, architecture, terrain, or decor. " + "Light sources and atmosphere are visible inside the scene, so the image feels grounded rather than empty. " + "Describe the space as if it were being framed for a finished image: floor or ground texture, wall or horizon " + "shape, nearby props, distant silhouettes, openings, furniture, signage, vegetation, machinery, weather, smoke, " + "mist, dust, or reflections where they fit the source idea. The background should not be a vague backdrop; it " + "should contain concrete objects at different distances, visible occlusion, scale changes, and a sense of air " + "between the subject and the farthest shapes. Mention how the environment affects the mood through color, " + "temperature, shadow direction, and the kind of light falling across the scene." + ) + + +def _z_image_base_fallback(word_salad, style_anchor, value): + terms = _z_image_terms(word_salad, style_anchor, value, limit=10) + mood = _sentence_from_terms(terms[:4], "the core idea") + return ( + f"The image is written as a natural Z-Image scene description with detailed sentences rather than SDXL tag lists. " + f"The visual treatment frames {mood} with cinematic composition, atmospheric lighting, realistic shadows, and a coherent color mood. " + "The camera language can suggest an eye-level view, three-quarter composition, 85mm lens, shallow depth of field, " + "or wide-angle framing when it fits the scene. State the style as a coherent visual direction, such as " + "photographic realism, painterly fantasy illustration, polished anime-inspired rendering, or editorial concept " + "art. Include contrast, color palette, texture handling, and the relationship between sharp focal areas and " + "softer peripheral details." + ) + + +def _extend_z_image_section(section, fallback, min_words): + section = _clean_z_image_text(section) + fallback = _clean_z_image_text(fallback) + if not section: + return fallback + if _word_count(section) >= min_words: + return section + if fallback and fallback.lower() not in section.lower(): + section = f"{section}. {fallback}".strip(" .") + "." + return _clean_z_image_text(section) + + +def _z_image_prompt_parts(value, word_salad, style_anchor, provided=None): + provided = provided or {} + combined = _join_positive_parts( + "z_image", + provided.get("base_prompt", ""), + provided.get("foreground_prompt", ""), + provided.get("background_prompt", ""), + ) or str(value) + + foreground = _clean_z_image_text(provided.get("foreground_prompt", "")) + foreground_fallback = _z_image_foreground_fallback(word_salad, style_anchor, "") + if not _z_image_section_is_usable(foreground): + foreground = foreground_fallback + foreground = _extend_z_image_section(foreground, foreground_fallback, Z_IMAGE_FOREGROUND_MIN_WORDS) + + background = _clean_z_image_text(provided.get("background_prompt", "")) + background_fallback = _z_image_background_fallback(word_salad, style_anchor, "") + if not _z_image_section_is_usable(background): + background = background_fallback + background = _extend_z_image_section(background, background_fallback, Z_IMAGE_BACKGROUND_MIN_WORDS) + + base = _clean_z_image_text(provided.get("base_prompt", "")) + base_fallback = _z_image_base_fallback(word_salad, style_anchor, "") + if not _z_image_section_is_usable(base, min_words=15): + base = base_fallback + base = _extend_z_image_section(base, base_fallback, Z_IMAGE_BASE_MIN_WORDS) + + return base, foreground, background + + +def _danbooru_tags_from_terms(terms): + tags = [] + seen = set() + for term in terms: + tag = re.sub(r"[^a-z0-9_ ]+", "", term.lower().replace("-", " ")).strip().replace(" ", "_") + if not tag or tag in seen or tag in STOPWORDS: + continue + seen.add(tag) + tags.append(tag) + if len(tags) >= 15: + break + + defaults = ("solo", "character", "detailed_background", "looking_at_viewer", "standing") + for tag in defaults: + if len(tags) >= 7: + break + if tag not in seen: + tags.append(tag) + seen.add(tag) + return tags[:15] + + +def _needs_pony_v7_structure(prompt): + prompt = str(prompt) + lowered = prompt.lower() + has_bad_numbered_tags = bool(re.search(r"\btag\s*\d+\s*:", lowered)) + has_repeated_control_tags = ( + len(re.findall(r"\brating_[a-z0-9_]+\b", lowered)) > 1 + or len(re.findall(r"\bstyle_cluster_\d+\b", lowered)) > 1 + or len(re.findall(r"\bscore_9\b", lowered)) > 1 + ) + has_visible_section_labels = any( + marker in lowered + for marker in ( + "# factual description", + "# stylistic description", + "# danbooru tags", + "1. foreground:", + "2. background:", + ) + ) + has_generic_background = any( + marker in lowered + for marker in ( + "coherent environment with visible depth", + "supporting background elements", + "background supports the subject", + "clear spatial layering and does not overpower", + ) + ) + has_special_tags = "score_9" in lowered and "rating_" in lowered and "style_cluster_" in lowered + return ( + _word_count(prompt) < 220 + or not has_special_tags + or has_repeated_control_tags + or has_bad_numbered_tags + or has_visible_section_labels + or has_generic_background + ) + + +def _join_positive_parts(target_profile, base_prompt, foreground_prompt, background_prompt): + profile = _normalize_target_profile(target_profile) + if profile == "z_image": + parts = [foreground_prompt, background_prompt, base_prompt] + return "\n\n".join(str(part).strip() for part in parts if str(part).strip()) + if profile == "pony_v7": + base_sections = [part.strip() for part in re.split(r"\n\s*\n+", str(base_prompt).strip()) if part.strip()] + header = "" + base_remainder = [] + for index, section in enumerate(base_sections): + if index == 0 and section.lower().startswith("score_"): + header = section + else: + base_remainder.append(section) + parts = [header, foreground_prompt, background_prompt, *base_remainder] + return "\n\n".join(str(part).strip() for part in parts if str(part).strip()) + parts = [str(part).strip() for part in (base_prompt, foreground_prompt, background_prompt) if str(part).strip()] + return " ".join(parts) + + +def _looks_like_tag_list(value): + text = str(value).strip() + if not text or "." in text: + return False + parts = [part.strip() for part in text.split(",") if part.strip()] + return len(parts) >= 3 + + +def _split_structured_pony_v7_prompt(value, word_salad, style_anchor, style_cluster=DEFAULT_STYLE_CLUSTER): + style_cluster = _normalize_style_cluster(style_cluster) + normalized = re.sub(r"style_cluster_\d+", f"style_cluster_{style_cluster}", str(value).strip()) + paragraphs = [part.strip() for part in re.split(r"\n\s*\n+", normalized) if part.strip()] + + header = f"score_9, rating_explicit, style_cluster_{style_cluster}" + base_parts = [] + foreground_parts = [] + background_parts = [] + + for index, paragraph in enumerate(paragraphs): + lowered = paragraph.lower() + if index == 0 and lowered.startswith("score_"): + base_parts.append(paragraph) + elif _looks_like_pony_v7_background_paragraph(paragraph): + background_parts.append(paragraph) + elif _looks_like_pony_v7_base_meta(paragraph) or _looks_like_tag_list(paragraph) or _looks_like_pony_v7_style_paragraph(paragraph): + base_parts.append(paragraph) + elif not foreground_parts: + foreground_parts.append(paragraph) + else: + base_parts.append(paragraph) + + if not any(part.lower().startswith("score_") for part in base_parts): + base_parts.insert(0, header) + + foreground = " ".join(foreground_parts).strip() + if not foreground: + foreground = _clean_unstructured_sentence(value) + if not foreground or _word_count(foreground) < 8: + foreground_terms, _, _, _ = _split_pony_v7_terms(word_salad, style_anchor, value) + foreground_terms = _pony_v7_foreground_terms(foreground_terms) + foreground = ( + "The image shows " + + _sentence_from_terms( + foreground_terms[:10], + "a clearly defined character with readable pose, silhouette, and visible design details", + ) + + "." + ) + + background = " ".join(background_parts).strip() + if not background or _word_count(background) < 8: + _, background_terms, _, _ = _split_pony_v7_terms(word_salad, style_anchor, value) + background = f"Behind the subject, the scene includes {_pony_v7_background_description(value, background_terms)}." + + _, _, style_terms, all_terms = _split_pony_v7_terms(word_salad, style_anchor, value) + tags = ", ".join(_danbooru_tags_from_terms(all_terms)) + if tags and not any(_looks_like_tag_list(part) for part in base_parts): + base_parts.append(tags) + if len(base_parts) == 1 and _word_count(base_parts[0]) < 8: + style_text = _sentence_from_terms(style_terms[:4], "digital anime illustration, clean linework, soft shading, atmospheric lighting") + base_parts.append(style_text) + + return "\n\n".join(part for part in base_parts if part).strip(), foreground, background + + +def _structured_pony_v7_parts(value, word_salad, style_anchor, style_cluster=DEFAULT_STYLE_CLUSTER): + if not _needs_pony_v7_structure(value): + cluster = _normalize_style_cluster(style_cluster) + normalized = re.sub(r"style_cluster_\d+", f"style_cluster_{cluster}", value) + return _split_structured_pony_v7_prompt(normalized, word_salad, style_anchor, cluster) + + style_cluster = _normalize_style_cluster(style_cluster) + foreground_terms, background_terms, style_terms, all_terms = _split_pony_v7_terms(word_salad, style_anchor, value) + foreground_sentence = "" + if _word_count(value) >= 12: + foreground_sentence = _clean_unstructured_sentence(value) + if not foreground_sentence or _word_count(foreground_sentence) < 8: + foreground_terms = _pony_v7_foreground_terms(foreground_terms) + foreground_sentence = ( + "The image shows " + + _sentence_from_terms( + foreground_terms[:10], + "a clearly defined character with readable pose, silhouette, and visible design details", + ) + + "." + ) + background = _pony_v7_background_description(value, background_terms) + style_fallback = "medium shot, balanced composition, and slight low angle perspective" + style_anchor_text = _sentence_from_terms(style_terms[:4], style_fallback) + if _word_count(style_anchor_text) < 3: + style_anchor_text = style_fallback + tags = ", ".join(_danbooru_tags_from_terms(all_terms)) + + base_prompt = ( + f"score_9, rating_explicit, style_cluster_{style_cluster}\n\n" + f"{style_anchor_text}. Digital anime illustration with clean linework, soft shading, controlled contrast, atmospheric lighting, and a focused color palette. The image uses depth of field and polished rendering to keep the main subject readable. Camera framing, line weight, shadow placement, highlight shape, and color separation should guide attention from the strongest focal point toward secondary details while keeping the scene finished and visually grounded.\n\n" + f"{tags}" + ) + foreground_prompt = ( + f"{foreground_sentence} The main subject is placed clearly in the foreground with readable form, controlled pose, and visible focal details. Material texture, expression, outfit details, and nearby props are described with enough specificity for a coherent image. The pose should make the action and body orientation understandable at a glance, with clear contact points, visible silhouette breaks, and readable spacing between limbs, clothing, accessories, and important objects. Surface details should reinforce the source concept through fabric shine, skin or fur texture, hard edges, soft edges, and small visual accents.\n\n" + ).strip() + background_prompt = ( + f"Behind the subject, the scene includes {background}. These setting details should be visibly placed behind and around the subject, giving the image clear spatial layering without overpowering the foreground. The background should contain identifiable middle-ground and far-background shapes, such as architecture, terrain, furniture, props, horizon elements, light sources, or atmospheric details from the source terms. It should feel like a real space with depth cues, occlusion, scale changes, and environmental color." + ) + return base_prompt, foreground_prompt, background_prompt + + +def _structured_pony_v7_prompt(value, word_salad, style_anchor, style_cluster=DEFAULT_STYLE_CLUSTER): + return _join_positive_parts( + "pony_v7", + *_structured_pony_v7_parts(value, word_salad, style_anchor, style_cluster), + ) + + +def _profile_positive_fallback(target_profile, word_salad, style_anchor, style_cluster): + target_profile = _normalize_target_profile(target_profile) + anchor = str(style_anchor).strip() + salad_terms = _curated_terms(word_salad) + core_terms = ", ".join(salad_terms) if salad_terms else "coherent anime subject, clean composition" + anchor_prefix = f"{anchor}, " if anchor else "" + + if target_profile == "pony_v6": + return f"score_9, score_8_up, score_7_up, {anchor_prefix}{core_terms}" + if target_profile == "illustrious": + return f"masterpiece, best_quality, {anchor_prefix}{core_terms}" + if target_profile == "z_image": + base_prompt, foreground_prompt, background_prompt = _z_image_prompt_parts( + "", + word_salad, + style_anchor, + None, + ) + return _join_positive_parts("z_image", base_prompt, foreground_prompt, background_prompt) + + phrase = ", ".join(salad_terms[:12]) if salad_terms else "a coherent anime subject with clean visual focus" + return _structured_pony_v7_prompt( + f"A polished anime illustration featuring {anchor_prefix}{phrase}, with coherent composition, detailed lighting, and a clear visual focus.", + word_salad, + style_anchor, + style_cluster, + ) + + +def _local_fallback_result(target_profile, word_salad, style_anchor, style_cluster, error_message): + target_profile = _normalize_target_profile(target_profile) + values = { + "positive": _profile_positive_fallback(target_profile, word_salad, style_anchor, style_cluster), + "negative": ", ".join(NEGATIVE_BASELINES[target_profile]), + "report": ( + "Ollama returned invalid JSON, so a local fallback prompt was built from curated input terms. " + f"Parser detail: {error_message}" + ), + } + return _postprocess_result(values, target_profile, word_salad, style_anchor, style_cluster) + + +def _target_profile_instructions(target_profile, style_cluster): + target_profile = _normalize_target_profile(target_profile) + style_cluster = _normalize_style_cluster(style_cluster) + if target_profile == "pony_v6": + return """Target profile: Pony v6. +- base_prompt must be 10 to 20 words and contain Pony v6 score tags plus broad model/style anchors. +- foreground_prompt must be about 36 to 40 words of concrete SDXL/Pony v6 subject, action, material, pose, expression, and focal-detail tags. +- background_prompt must be about 36 to 40 words of concrete setting, prop, atmosphere, architecture, terrain, and depth tags. +- Use space-separated tags without commas in base_prompt, foreground_prompt, and background_prompt. +- Include only concrete subject, action, material, setting, prop, composition, and a few useful quality tags. +- Avoid filler words, prose sentences, random process words, generic style spam, and placeholders like simple_background.""" + if target_profile == "illustrious": + return """Target profile: Illustrious. +- base_prompt must be 10 to 20 words and contain compact IllustriousXL quality, style, artist/medium, and global aesthetic tags. +- foreground_prompt must be about 36 to 40 words of concrete subject, action, outfit/material, pose, expression, and focal-detail tags. +- background_prompt must be about 36 to 40 words of concrete setting, room, architecture, prop, decor, atmosphere, lighting, and depth tags. +- Use space-separated tags without commas in base_prompt, foreground_prompt, and background_prompt. +- Do not copy the word salad literally. +- Avoid process words, useless random objects, generic style spam, and placeholders like simple_background.""" + if target_profile == "z_image": + return """Target profile: Z-Image. +- Write natural, detailed image-description prose, not SDXL/Danbooru tag lists. +- Write enough complete sentences for a long Z-Image prompt, about 360 to 440 words total after the three positive fields are joined. +- base_prompt describes medium, visual style, camera/framing, lighting approach, color mood, rendering language, lens behavior, focus, contrast, and texture handling in about 80 to 100 words. +- foreground_prompt describes the main subject, action, posture, visible materials, expression, clothing or surface details, focal details, contact points, and physical readability in about 130 to 160 words. +- background_prompt describes the environment, scene context, light sources, atmosphere, architecture/terrain/props, depth cues, distant objects, and spatial layering in about 130 to 160 words. +- positive is built locally from foreground_prompt, background_prompt, then base_prompt and should read like one detailed 400 word Z-Image prompt. +- negative should be an empty string because Z-Image Turbo does not use negative prompts. +- Do not use control tags such as score_9, source_anthro, rating_explicit, style_cluster, masterpiece, best quality, 8k, or tag spam. +- Do not include visible section labels.""" + return f"""Target profile: Pony v7. +- base_prompt must start with: score_9, rating_explicit, style_cluster_{style_cluster} +- base_prompt must also contain global model/source/style tags, medium, lighting, camera, and rendering guidance. +- foreground_prompt must be a rich concrete foreground caption: subject, body/clothing/materials, pose, action, expression, props, and focal details. +- background_prompt must be a rich concrete background caption: place, room, architecture, terrain, decor, weather, light sources, distant objects, and depth cues. +- Do not include these labels: "# factual description", "1. Foreground:", "2. Background:", "# stylistic description", "# danbooru tags:". +- background_prompt must name concrete visible setting details from the source words. Do not use generic placeholders like "coherent environment" or "supporting background elements". +- Keep rating_explicit in the first line when explicit content is requested or implied. +- base_prompt + foreground_prompt + background_prompt should be about 220 to 300 words total.""" + + +def _build_generation_prompt(target_profile, word_salad, style_anchor, style_cluster): + target_profile = _normalize_target_profile(target_profile) + salad_terms = _curated_terms(word_salad) + anchor = str(style_anchor).strip() + anchor_block = anchor if anchor else "(none)" + return f"""Rewrite this random word salad into one model-specific image prompt set. + +{_target_profile_instructions(target_profile, style_cluster)} + +Random word salad: +{str(word_salad).strip()} + +Style anchor / fixed requirements: +{anchor_block} + +Important: +- Fill every JSON field with a useful non-empty string, except Z-Image negative which should be empty. +- Negative fields must describe things to avoid, not repeat the positive prompt. For Z-Image only, negative must be an empty string. +- Do not treat Pony v6 or Pony v7 as animal subjects. +- Start your response with {{ and end it with }}. +- Use exactly the requested JSON keys, with no markdown and no commentary. +- Required JSON keys: {", ".join(RESPONSE_KEYS)}. + +Return valid JSON only.""" + + +def _build_repair_prompt(raw_response): + return f"""Repair the following invalid answer into valid JSON only. +It must contain exactly these string keys: +{", ".join(RESPONSE_KEYS)} +Every value except negative must be a non-empty string. +Negative fields must be real negative prompts unless the target instructions say the model uses no negative prompt. + +Invalid answer: +{raw_response}""" + + +def _build_minimal_retry_prompt(target_profile, word_salad, style_anchor, style_cluster): + terms = ", ".join(_curated_terms(f"{style_anchor} {word_salad}", limit=18)) + if not terms: + terms = "anime subject, clean composition, detailed lighting" + target_profile = _normalize_target_profile(target_profile) + return f"""Return one JSON object only. No markdown. No prose. +Use exactly these keys: {", ".join(RESPONSE_KEYS)} +{_target_profile_instructions(target_profile, style_cluster)} +Source visual terms: {terms} +Rules: base_prompt contains global model/style or natural style guidance; foreground_prompt describes the main subject; background_prompt describes the visible setting; negative lists quality/anatomy/artifact problems to avoid unless the target profile says it is unused; report is one short sentence.""" + + +class NukunOllamaPromptRefiner: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "word_salad": ( + "STRING", + { + "default": "", + "multiline": True, + "dynamicPrompts": True, + "defaultInput": True, + "tooltip": "Random vocabulary text to curate into model-specific prompts.", + }, + ), + "ollama_url": ( + "STRING", + { + "default": DEFAULT_OLLAMA_URL, + "multiline": False, + "tooltip": "Base Ollama URL, usually http://127.0.0.1:11434.", + }, + ), + "ollama_model": ( + _available_ollama_models(), + { + "default": DEFAULT_OLLAMA_MODEL, + "tooltip": "Local Ollama model used to rewrite the prompt. Start Ollama before ComfyUI to refresh this list.", + }, + ), + "target_profile": ( + TARGET_PROFILES, + { + "default": DEFAULT_TARGET_PROFILE, + "tooltip": "Prompt profile to generate. Ollama only writes this one split prompt set per run.", + }, + ), + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff, + "control_after_generate": True, + "tooltip": "Seed passed to Ollama for repeatable prompt rewriting.", + }, + ), + "temperature": ( + "FLOAT", + { + "default": 0.45, + "min": 0.0, + "max": 2.0, + "step": 0.01, + "tooltip": "Ollama generation temperature. Lower is more deterministic.", + }, + ), + "top_p": ( + "FLOAT", + { + "default": 0.9, + "min": 0.01, + "max": 1.0, + "step": 0.01, + "tooltip": "Ollama nucleus sampling value.", + }, + ), + "style_cluster": ( + "INT", + { + "default": DEFAULT_STYLE_CLUSTER, + "min": 0, + "max": 2048, + "tooltip": "Pony v7 style_cluster number used in the structured prompt header.", + }, + ), + "timeout_seconds": ( + "INT", + { + "default": 120, + "min": 1, + "max": 600, + "tooltip": "Maximum time to wait for each Ollama request.", + }, + ), + "context_length": ( + "INT", + { + "default": DEFAULT_OLLAMA_CONTEXT_LENGTH, + "min": 512, + "max": 262144, + "step": 512, + "tooltip": "Ollama num_ctx context window. Higher values need more VRAM/RAM and may be limited by the selected model.", + }, + ), + }, + "optional": { + "style_anchor": ( + "STRING", + { + "default": "", + "multiline": True, + "dynamicPrompts": True, + "defaultInput": True, + "tooltip": "Optional fixed motifs, character names, LoRA tags, or quality tags to preserve.", + }, + ), + }, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING") + RETURN_NAMES = OUTPUT_KEYS + FUNCTION = "refine" + CATEGORY = "Nukun/Text" + DESCRIPTION = "Uses a local Ollama model to turn random vocabulary into one selected Pony v6, Illustrious, Pony v7, or Z-Image split prompt set." + + def refine( + self, + word_salad, + ollama_url, + ollama_model, + target_profile, + seed, + temperature, + top_p, + style_cluster, + timeout_seconds, + context_length=DEFAULT_OLLAMA_CONTEXT_LENGTH, + style_anchor="", + ): + if not str(word_salad).strip() and not str(style_anchor).strip(): + raise RuntimeError("Ollama Prompt Refiner: word_salad or style_anchor must contain text") + + raw_response = _request_ollama( + ollama_url, + ollama_model, + _build_generation_prompt(target_profile, word_salad, style_anchor, style_cluster), + seed, + temperature, + top_p, + timeout_seconds, + context_length, + ) + + try: + values = _validate_result(_extract_json_object(raw_response)) + return _postprocess_result(values, target_profile, word_salad, style_anchor, style_cluster) + except ValueError as first_error: + repair_response = _request_ollama( + ollama_url, + ollama_model, + _build_repair_prompt(raw_response), + int(seed) + 1, + 0.0, + 1.0, + timeout_seconds, + context_length, + ) + try: + values = _validate_result(_extract_json_object(repair_response)) + return _postprocess_result(values, target_profile, word_salad, style_anchor, style_cluster) + except ValueError as second_error: + minimal_response = _request_ollama( + ollama_url, + ollama_model, + _build_minimal_retry_prompt(target_profile, word_salad, style_anchor, style_cluster), + int(seed) + 2, + 0.0, + 1.0, + timeout_seconds, + context_length, + ) + try: + values = _validate_result(_extract_json_object(minimal_response)) + return _postprocess_result(values, target_profile, word_salad, style_anchor, style_cluster) + except ValueError as third_error: + return _local_fallback_result( + target_profile, + word_salad, + style_anchor, + style_cluster, + f"initial={first_error}; repair={second_error}; minimal={third_error}", + ) + + @classmethod + def IS_CHANGED( + cls, + word_salad, + ollama_url, + ollama_model, + target_profile, + seed, + temperature, + top_p, + style_cluster, + timeout_seconds, + context_length=DEFAULT_OLLAMA_CONTEXT_LENGTH, + style_anchor="", + ): + digest = hashlib.sha256() + for value in ( + word_salad, + ollama_url, + ollama_model, + _normalize_target_profile(target_profile), + int(seed), + float(temperature), + float(top_p), + _normalize_style_cluster(style_cluster), + int(timeout_seconds), + _normalize_context_length(context_length), + style_anchor, + ): + digest.update(str(value).encode("utf-8")) + digest.update(b"\0") + return digest.hexdigest() + + +NODE_CLASS_MAPPINGS = { + "NukunOllamaPromptRefiner": NukunOllamaPromptRefiner, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "NukunOllamaPromptRefiner": "Ollama Prompt Refiner (Nukun)", +} diff --git a/nodes/random_vocab_string_list.py b/nodes/random_vocab_string_list.py index 39d82bb..65b90f9 100644 --- a/nodes/random_vocab_string_list.py +++ b/nodes/random_vocab_string_list.py @@ -4,8 +4,18 @@ import random import folder_paths +try: + from aiohttp import web + from server import PromptServer +except ImportError: + web = None + PromptServer = None + RESOURCE_EXTENSIONS = (".csv", ".txt", ".json") USER_VOCAB_LABEL = "user/vocab.json" +RANDOM_WORD_LABEL = "(random)" +MULTI_SLOT_COUNT = 4 +MULTI_WORD_COUNT = 3 def _vocab_path(): @@ -75,6 +85,65 @@ def _load_words(vocab_file): return words +def _dedupe_preserve_order(words): + seen = set() + result = [] + for word in words: + if word in seen: + continue + seen.add(word) + result.append(word) + return result + + +def _slot_seed(seed, slot_index): + return (int(seed) + (slot_index * 1000003)) & 0xffffffffffffffff + + +def _generate_slot(vocab_file, amount, seed, slot_index, selections): + amount = max(0, int(amount)) + if amount <= 0: + return "" + + words = _load_words(vocab_file) + wordset = set(words) + manual = [ + str(selection).strip() + for selection in selections + if str(selection).strip() and str(selection).strip() != RANDOM_WORD_LABEL + ] + manual = _dedupe_preserve_order([word for word in manual if word in wordset]) + selected = manual[:amount] + remaining_count = amount - len(selected) + + if remaining_count > 0: + available = [word for word in words if word not in set(selected)] + pick_count = min(remaining_count, len(available)) + if pick_count > 0: + selected.extend(random.Random(_slot_seed(seed, slot_index)).sample(available, pick_count)) + + return " ".join(selected) + + +def _register_routes(): + if web is None or PromptServer is None: + return + + routes = PromptServer.instance.routes + + @routes.get("/nukun/vocab/words") + async def get_vocab_words(request): + vocab_file = request.query.get("file", "") + try: + words = _load_words(vocab_file) + except RuntimeError as error: + return web.json_response({"file": vocab_file, "error": str(error)}, status=400) + return web.json_response({"file": vocab_file, "words": words}) + + +_register_routes() + + class NukunRandomVocabStringList: @classmethod def INPUT_TYPES(cls): @@ -158,10 +227,130 @@ class NukunRandomVocabStringList: return digest.hexdigest() +class NukunVocabMultiStringList: + @classmethod + def INPUT_TYPES(cls): + available_files = _available_vocab_files() + default_file = USER_VOCAB_LABEL if USER_VOCAB_LABEL in available_files else available_files[0] + + required = { + "seed": ( + "INT", + { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff, + "control_after_generate": True, + "tooltip": "Base seed for deterministic random selections across all slots.", + }, + ), + } + + for slot in range(1, MULTI_SLOT_COUNT + 1): + required[f"vocab_file_{slot}"] = ( + available_files, + { + "default": default_file, + "tooltip": f"Comma-separated word list for slot {slot}.", + }, + ) + required[f"amount_{slot}"] = ( + "INT", + { + "default": 3, + "min": 0, + "max": 10000, + "tooltip": f"Number of words to output from slot {slot}. Use 0 to disable the slot.", + }, + ) + for word_index in range(1, MULTI_WORD_COUNT + 1): + required[f"word_{slot}_{word_index}"] = ( + "STRING", + { + "default": RANDOM_WORD_LABEL, + "multiline": False, + "tooltip": f"Optional fixed word {word_index} for slot {slot}. Keep {RANDOM_WORD_LABEL} to fill randomly.", + }, + ) + + return { + "optional": { + "chain": ( + "STRING", + { + "default": "", + "defaultInput": True, + "forceInput": True, + "multiline": False, + "tooltip": "Optional incoming string. When connected, the combined output is appended after it.", + }, + ), + }, + "required": required, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING") + RETURN_NAMES = ("combined", "slot_1", "slot_2", "slot_3", "slot_4") + FUNCTION = "generate" + CATEGORY = "Nukun/Text" + DESCRIPTION = "Combines four selectable vocabulary files into one deterministic prompt string with optional fixed words per slot." + + def generate(self, seed, chain="", **kwargs): + slot_outputs = [] + for slot in range(1, MULTI_SLOT_COUNT + 1): + selections = [kwargs.get(f"word_{slot}_{word_index}", RANDOM_WORD_LABEL) for word_index in range(1, MULTI_WORD_COUNT + 1)] + slot_outputs.append( + _generate_slot( + kwargs[f"vocab_file_{slot}"], + kwargs[f"amount_{slot}"], + seed, + slot, + selections, + ) + ) + + combined_parts = [slot_output for slot_output in slot_outputs if slot_output] + combined = " ".join(combined_parts) + chain = chain.strip() + if chain and combined: + combined = f"{chain} {combined}" + elif chain: + combined = chain + + return (combined, *slot_outputs) + + @classmethod + def IS_CHANGED(cls, seed, chain="", **kwargs): + digest = hashlib.sha256() + digest.update(str(int(seed)).encode("utf-8")) + digest.update(str(chain).encode("utf-8")) + + for slot in range(1, MULTI_SLOT_COUNT + 1): + vocab_file = kwargs.get(f"vocab_file_{slot}", "") + digest.update(str(vocab_file).encode("utf-8")) + digest.update(str(int(kwargs.get(f"amount_{slot}", 0))).encode("utf-8")) + for word_index in range(1, MULTI_WORD_COUNT + 1): + digest.update(str(kwargs.get(f"word_{slot}_{word_index}", RANDOM_WORD_LABEL)).encode("utf-8")) + + try: + path = _resolve_vocab_path(vocab_file) + except RuntimeError: + path = "" + if os.path.exists(path): + with open(path, "rb") as vocab_file_handle: + digest.update(vocab_file_handle.read()) + else: + digest.update(b"missing") + + return digest.hexdigest() + + NODE_CLASS_MAPPINGS = { "NukunRandomVocabStringList": NukunRandomVocabStringList, + "NukunVocabMultiStringList": NukunVocabMultiStringList, } NODE_DISPLAY_NAME_MAPPINGS = { "NukunRandomVocabStringList": "Random Vocab String List (Nukun)", + "NukunVocabMultiStringList": "Multi Vocab String List (Nukun)", } diff --git a/nodes/t5_equal_length_balancer.py b/nodes/t5_equal_length_balancer.py index adb3382..830cce3 100644 --- a/nodes/t5_equal_length_balancer.py +++ b/nodes/t5_equal_length_balancer.py @@ -1,4 +1,20 @@ -SUPPORTED_T5_KEYS = ("pile_t5xl", "t5xxl", "umt5xxl", "mt5xl", "t5base") +SUPPORTED_TEXT_STREAM_KEYS = ( + "pile_t5xl", + "t5xxl", + "umt5xxl", + "mt5xl", + "t5base", + "qwen3_4b", + "qwen3_8b", + "qwen25_7b", + "qwen25_3b", + "qwen3_2b", + "qwen3_06b", + "qwen3vl_8b", +) + +# Backwards-compatible name for older imports. +SUPPORTED_T5_KEYS = SUPPORTED_TEXT_STREAM_KEYS class NukunT5EqualLengthBalancer: @@ -6,7 +22,7 @@ class NukunT5EqualLengthBalancer: def INPUT_TYPES(cls): return { "required": { - "tokenizer": ("CLIP", {"tooltip": "The loaded T5 tokenizer to use."}), + "tokenizer": ("CLIP", {"tooltip": "The loaded T5/Qwen tokenizer to use."}), "target": ( "INT", { @@ -34,24 +50,24 @@ class NukunT5EqualLengthBalancer: FUNCTION = "balance" CATEGORY = "Nukun/Conditioning" DESCRIPTION = ( - "Encodes positive and negative prompts with a supported T5 tokenizer, " + "Encodes positive and negative prompts with a supported T5/Qwen tokenizer, " "padding both to the same effective token length and returning token diagnostics." ) - def _set_t5_options(self, clip, min_length=None, min_padding=None): - for key in SUPPORTED_T5_KEYS: + def _set_text_stream_options(self, clip, min_length=None, min_padding=None): + for key in SUPPORTED_TEXT_STREAM_KEYS: if min_length is not None: clip.set_tokenizer_option(f"{key}_min_length", min_length) if min_padding is not None: clip.set_tokenizer_option(f"{key}_min_padding", min_padding) - def _detect_t5_key(self, *token_sets): + def _detect_text_stream_key(self, *token_sets): available = set() for tokens in token_sets: if isinstance(tokens, dict): available.update(tokens.keys()) - for key in SUPPORTED_T5_KEYS: + for key in SUPPORTED_TEXT_STREAM_KEYS: if key in available: return key return None @@ -79,27 +95,27 @@ class NukunT5EqualLengthBalancer: raise RuntimeError("ERROR: A valid tokenizer is required.") measure = tokenizer.clone() - self._set_t5_options(measure, min_length=0, min_padding=0) + self._set_text_stream_options(measure, min_length=0, min_padding=0) raw_positive_tokens = measure.tokenize(positive) raw_negative_tokens = measure.tokenize(negative) - t5_key = self._detect_t5_key(raw_positive_tokens, raw_negative_tokens) - if t5_key is None: - expected = ", ".join(SUPPORTED_T5_KEYS) + stream_key = self._detect_text_stream_key(raw_positive_tokens, raw_negative_tokens) + if stream_key is None: + expected = ", ".join(SUPPORTED_TEXT_STREAM_KEYS) available = self._available_token_keys(raw_positive_tokens, raw_negative_tokens) raise RuntimeError( - "ERROR: No supported T5 token stream found. " + "ERROR: No supported text token stream found. " f"Expected one of: {expected}. Available token streams: {available}." ) - positive_raw_count = self._token_count(raw_positive_tokens, t5_key) - negative_raw_count = self._token_count(raw_negative_tokens, t5_key) + positive_raw_count = self._token_count(raw_positive_tokens, stream_key) + negative_raw_count = self._token_count(raw_negative_tokens, stream_key) requested_target = max(0, int(target)) effective_target = max(requested_target, positive_raw_count, negative_raw_count) encoder = tokenizer.clone() - self._set_t5_options(encoder, min_padding=0) - encoder.set_tokenizer_option(f"{t5_key}_min_length", effective_target) + self._set_text_stream_options(encoder, min_padding=0) + encoder.set_tokenizer_option(f"{stream_key}_min_length", effective_target) positive_tokens = encoder.tokenize(positive) negative_tokens = encoder.tokenize(negative) @@ -107,7 +123,7 @@ class NukunT5EqualLengthBalancer: cond_negative = encoder.encode_from_tokens_scheduled(negative_tokens) report = ( - f"T5 key: {t5_key}; " + f"text stream: {stream_key}; " f"positive raw: {positive_raw_count}; " f"negative raw: {negative_raw_count}; " f"requested target: {requested_target}; " @@ -129,5 +145,5 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { - "NukunT5EqualLengthBalancer": "T5 Equal-Length Prompt Balancer (Nukun)", + "NukunT5EqualLengthBalancer": "T5/Qwen Equal-Length Prompt Balancer (Nukun)", } diff --git a/nodes/t5_sculpt_equal_length_balancer.py b/nodes/t5_sculpt_equal_length_balancer.py index 5287ea4..f2e3d07 100644 --- a/nodes/t5_sculpt_equal_length_balancer.py +++ b/nodes/t5_sculpt_equal_length_balancer.py @@ -3,7 +3,7 @@ import numbers import torch import comfy.model_management as model_management -from .t5_equal_length_balancer import SUPPORTED_T5_KEYS +from .t5_equal_length_balancer import SUPPORTED_TEXT_STREAM_KEYS SCULPT_METHODS = ("forward", "backward", "maximum_absolute", "add_minimum_absolute") @@ -26,7 +26,7 @@ class NukunT5SculptEqualLengthBalancer: def INPUT_TYPES(cls): return { "required": { - "tokenizer": ("CLIP", {"tooltip": "The loaded T5 tokenizer to use."}), + "tokenizer": ("CLIP", {"tooltip": "The loaded T5/Qwen tokenizer to use."}), "target": ( "INT", { @@ -78,24 +78,24 @@ class NukunT5SculptEqualLengthBalancer: FUNCTION = "balance" CATEGORY = "Nukun/Conditioning" DESCRIPTION = ( - "Encodes positive and negative prompts with equal T5 token length, " - "optionally sculpting eligible T5 token embeddings before scheduled encoding." + "Encodes positive and negative prompts with equal T5/Qwen token length, " + "optionally sculpting eligible text token embeddings before scheduled encoding." ) - def _set_t5_options(self, clip, min_length=None, min_padding=None): - for key in SUPPORTED_T5_KEYS: + def _set_text_stream_options(self, clip, min_length=None, min_padding=None): + for key in SUPPORTED_TEXT_STREAM_KEYS: if min_length is not None: clip.set_tokenizer_option(f"{key}_min_length", min_length) if min_padding is not None: clip.set_tokenizer_option(f"{key}_min_padding", min_padding) - def _detect_t5_key(self, *token_sets): + def _detect_text_stream_key(self, *token_sets): available = set() for tokens in token_sets: if isinstance(tokens, dict): available.update(tokens.keys()) - for key in SUPPORTED_T5_KEYS: + for key in SUPPORTED_TEXT_STREAM_KEYS: if key in available: return key return None @@ -118,14 +118,16 @@ class NukunT5SculptEqualLengthBalancer: except (KeyError, TypeError): raise RuntimeError(f"ERROR: Token stream '{key}' was not found in tokenizer output.") - def _get_t5_submodel(self, clip, t5_key): - submodel = getattr(clip.cond_stage_model, t5_key, None) + def _get_text_submodel(self, clip, stream_key): + submodel = getattr(clip.cond_stage_model, stream_key, None) if submodel is None: - raise RuntimeError(f"ERROR: T5 submodel '{t5_key}' was not found on cond_stage_model.") + raise RuntimeError(f"ERROR: Text encoder submodel '{stream_key}' was not found on cond_stage_model.") if not hasattr(submodel, "transformer"): - raise RuntimeError(f"ERROR: T5 submodel '{t5_key}' has no transformer.") + raise RuntimeError(f"ERROR: Text encoder submodel '{stream_key}' has no transformer.") if not hasattr(submodel.transformer, "get_input_embeddings"): - raise RuntimeError(f"ERROR: T5 submodel '{t5_key}' has no input embeddings accessor.") + raise RuntimeError( + f"ERROR: Text encoder submodel '{stream_key}' has no input embeddings accessor." + ) return submodel def _special_token_ids(self, submodel): @@ -229,9 +231,9 @@ class NukunT5SculptEqualLengthBalancer: new_weight = new_weight * pre_mag / new_norm return new_weight.detach().cpu(), len(selected_weights) - def _eligible_entries(self, tokens, t5_key, special_ids): + def _eligible_entries(self, tokens, stream_key, special_ids): coords = [] - for batch_index, batch in enumerate(tokens[t5_key]): + for batch_index, batch in enumerate(tokens[stream_key]): for token_index, token_weight in enumerate(batch): if len(token_weight) < 2: continue @@ -243,15 +245,15 @@ class NukunT5SculptEqualLengthBalancer: coords.append((batch_index, token_index, int(token_id))) return coords - def _apply_sculpting(self, tokens, t5_key, submodel, intensity, method, normalization, top_k): + def _apply_sculpting(self, tokens, stream_key, submodel, intensity, method, normalization, top_k): if float(intensity) <= 0 and normalization == "none": return 0, 0 - if t5_key not in tokens: - raise RuntimeError(f"ERROR: Token stream '{t5_key}' was not found for sculpting.") + if stream_key not in tokens: + raise RuntimeError(f"ERROR: Token stream '{stream_key}' was not found for sculpting.") special_ids = self._special_token_ids(submodel) - coords = self._eligible_entries(tokens, t5_key, special_ids) + coords = self._eligible_entries(tokens, stream_key, special_ids) if len(coords) == 0: return 0, 0 @@ -263,7 +265,7 @@ class NukunT5SculptEqualLengthBalancer: mean_coords = [] for batch_index, token_index, token_id in coords: - token_weight = tokens[t5_key][batch_index][token_index] + token_weight = tokens[stream_key][batch_index][token_index] attn_weight = float(token_weight[1]) if float(intensity) > 0: cache_key = (token_id, method, float(intensity), int(top_k)) @@ -282,17 +284,17 @@ class NukunT5SculptEqualLengthBalancer: mean_mag += torch.norm(new_vector).item() mean_coords.append((batch_index, token_index, attn_weight)) - tokens[t5_key][batch_index][token_index] = (new_vector, attn_weight) + tokens[stream_key][batch_index][token_index] = (new_vector, attn_weight) if normalization in ("mean", "mean * attention") and len(mean_coords) > 0: mean_mag /= len(mean_coords) for batch_index, token_index, attn_weight in mean_coords: - token_vector, current_attn = tokens[t5_key][batch_index][token_index] + token_vector, current_attn = tokens[stream_key][batch_index][token_index] norm = torch.norm(token_vector) if norm == 0: continue scale = mean_mag * (attn_weight if normalization == "mean * attention" else 1.0) - tokens[t5_key][batch_index][token_index] = (token_vector / norm * scale, current_attn) + tokens[stream_key][batch_index][token_index] = (token_vector / norm * scale, current_attn) del all_weights return sculpted_count, neighbor_count @@ -315,35 +317,35 @@ class NukunT5SculptEqualLengthBalancer: raise RuntimeError("ERROR: A valid tokenizer is required.") measure = tokenizer.clone() - self._set_t5_options(measure, min_length=0, min_padding=0) + self._set_text_stream_options(measure, min_length=0, min_padding=0) raw_positive_tokens = measure.tokenize(positive) raw_negative_tokens = measure.tokenize(negative) - t5_key = self._detect_t5_key(raw_positive_tokens, raw_negative_tokens) - if t5_key is None: - expected = ", ".join(SUPPORTED_T5_KEYS) + stream_key = self._detect_text_stream_key(raw_positive_tokens, raw_negative_tokens) + if stream_key is None: + expected = ", ".join(SUPPORTED_TEXT_STREAM_KEYS) available = self._available_token_keys(raw_positive_tokens, raw_negative_tokens) raise RuntimeError( - "ERROR: No supported T5 token stream found. " + "ERROR: No supported text token stream found. " f"Expected one of: {expected}. Available token streams: {available}." ) - positive_raw_count = self._token_count(raw_positive_tokens, t5_key) - negative_raw_count = self._token_count(raw_negative_tokens, t5_key) + positive_raw_count = self._token_count(raw_positive_tokens, stream_key) + negative_raw_count = self._token_count(raw_negative_tokens, stream_key) requested_target = max(0, int(target)) effective_target = max(requested_target, positive_raw_count, negative_raw_count) encoder = tokenizer.clone() - self._set_t5_options(encoder, min_padding=0) - encoder.set_tokenizer_option(f"{t5_key}_min_length", effective_target) - submodel = self._get_t5_submodel(encoder, t5_key) + self._set_text_stream_options(encoder, min_padding=0) + encoder.set_tokenizer_option(f"{stream_key}_min_length", effective_target) + submodel = self._get_text_submodel(encoder, stream_key) positive_tokens = encoder.tokenize(positive) negative_tokens = encoder.tokenize(negative) positive_sculpted, positive_neighbors = self._apply_sculpting( positive_tokens, - t5_key, + stream_key, submodel, positive_intensity, positive_method, @@ -352,7 +354,7 @@ class NukunT5SculptEqualLengthBalancer: ) negative_sculpted, negative_neighbors = self._apply_sculpting( negative_tokens, - t5_key, + stream_key, submodel, negative_intensity, negative_method, @@ -364,7 +366,7 @@ class NukunT5SculptEqualLengthBalancer: cond_negative = encoder.encode_from_tokens_scheduled(negative_tokens) report = ( - f"T5 key: {t5_key}; " + f"text stream: {stream_key}; " f"positive raw: {positive_raw_count}; " f"negative raw: {negative_raw_count}; " f"requested target: {requested_target}; " @@ -395,5 +397,5 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { - "NukunT5SculptEqualLengthBalancer": "T5 Sculpt Equal-Length Prompt Balancer (Nukun)", + "NukunT5SculptEqualLengthBalancer": "T5/Qwen Sculpt Equal-Length Prompt Balancer (Nukun)", } diff --git a/resources/adjectives.csv b/resources/adjectives.csv index d974f6e..34680d2 100644 --- a/resources/adjectives.csv +++ b/resources/adjectives.csv @@ -1 +1 @@ -abstract, ancient, angry, artificial, beautiful, big, black, blue, bright, broken, calm, cinematic, clean, clear, cold, colorful, colourful, complex, cool, cute, dark, deep, delicate, dense, detailed, dramatic, dry, empty, epic, experimental, fast, fine, flat, fresh, full, gentle, giant, glowing, golden, good, green, happy, hard, heavy, high, huge, light, little, long, loose, loud, low, magic, magical, modern, narrow, natural, new, old, open, orange, pale, peaceful, pink, pretty, quiet, random, red, rich, rough, round, sad, sharp, shiny, short, simple, slow, small, smooth, soft, strong, subtle, tall, tiny, transparent, warm, weak, wet, white, wide, wild, yellow, young +abandoned, abnormal, abrasive, absolute, abstract, abysmal, accidental, accurate, acidic, acoustic, acrid, active, adamant, adaptive, adhesive, adjacent, adorable, advanced, adventurous, adverse, aerial, aerodynamic, affected, affectionate, affordable, aged, aggressive, agile, airborne, airy, alabaster, alarming, alchemical, alert, alien, alienated, alive, all_weather, alluring, almighty, alpine, alternative, amateur, amber, ambient, ambiguous, amorous, amorphous, ample, analog, ancestral, ancient, ancient_looking, androgynous, angelic, angry, angular, animalistic, animated, antique, anxious, apocalyptic, appealing, appetizing, approachable, aquatic, arcane, arched, architectural, arctic, armored, aromatic, artificial, artistic, ash, ash_gray, ashen, astronomical, asymmetrical, athletic, atmospheric, atomic, audible, auroral, austere, authentic, automated, automatic, autumn, autumnal, avant_garde, awestruck, azure, baby_blue, backlit, bad, baggy, baked, balanced, banded, barbed, bare, baroque, barren, bashful, basic, battered, battle_damaged, battle_ready, battle_worn, beaded, beautiful, beige, bejeweled, beloved, bent, bestial, big, billowing, biohazard, bioluminescent, bitter, bizarre, black, black_and_gold, black_and_red, black_and_white, blackened, blank, blasted, blazing, bleak, bleeding, blended, blessed, blind, blinding, blissful, blistered, blobby, blocky, blood_red, bloodstained, bloody, blue, blue_gray, blue_green, blunt, blushing, bohemian, boiling, bold, bone, bone_dry, bone_white, boring, botanical, braided, branching, brass, brassy, brave, breathless, breezy, brick_red, bright, brilliant, brittle, broad, broken, bronze, brooding, brown, brownish, bruised, brushed, brutal, brutalist, bubbly, bulky, burning, burnt, burnt_orange, busy, byzantine, calculated, calm, camouflaged, candid, candlelit, candy_colored, caramel, carmine, cartoonish, carved, casual, cautious, cavernous, cel_shaded, celestial, ceremonial, chalk, chalky, changing, chaotic, charcoal, charming, cheap, checkered, cheerful, childlike, chilly, chipped, chiseled, chromatic, chrome, chunky, cinematic, circular, civilized, classic, classy, clay, clean, clear, closed, clouded, cloudy, cluttered, coarse, cobalt, coherent, cold, cold_blooded, colorful, colorless, colossal, colourful, colourless, comfortable, comic, compact, complete, complex, composed, concealed, confident, confusing, conservative, constant, contemporary, controlled, convincing, cool, copper, coppery, coral, corporate, corrupted, cosmic, cotton, cozy, cracked, crafty, cream, creamy, creative, creepy, crimson, crinkled, crisp, critical, crooked, cropped, crowded, cruel, crumbling, crushed, cryptic, crystal, crystalline, cursed, curved, curvy, cute, cyan, cybernetic, damaged, damp, dangerous, daring, dark, dawn, daylight, dazzling, dead, deadly, decadent, decayed, decorative, deep, deep_blue, deep_red, defiant, deformed, degraded, delicate, demonic, dense, derelict, desaturated, desert, detailed, dieselpunk, digital, dimensional, dirty, disheveled, dissolved, distant, distorted, divine, dizzy, double, dramatic, drawn, dreamlike, dreamy, dripping, dry, dual, dull, dusk, dusty, dynamic, dystopian, earthy, eccentric, eerie, eldritch, electric, electric_blue, elegant, elevated, elongated, embossed, emerald, emotional, empty, enchanted, endless, energetic, engraved, enormous, epic, eroded, ethereal, exaggerated, experimental, expressionist, expressive, extra_large, extreme, faded, faithful, familiar, famous, fancy, fantastic, far, fast, feathered, feral, festive, fibrous, fictional, fierce, fiery, filigree, fine, fish_eye, flame_colored, flat, flawless, fleshy, floral, fluffy, foggy, folded, forlorn, fractured, fragile, frantic, freckled, fresh, frigid, frosted, frozen, full, functional, futuristic, fuzzy, gargantuan, gelatinous, gentle, geometric, ghostlike, ghostly, giant, gilded, glass, glassy, gloomy, glossy, glowing, glowing_blue, gold, golden, good, gorgeous, gothic, graceful, grainy, grand, granular, graphite, gray, grayscale, greasy, green, greenish, grim, grimy, gritty, grotesque, grungy, hairless, hairy, half_visible, handcrafted, handmade, happy, hard, haunted, haunting, hazy, heartbroken, heavenly, heavy, heroic, hidden, high, high_contrast, historical, hollow, holographic, hooded, horizontal, hot, huge, humanoid, humble, hungry, hyperrealistic, iconic, icy, ideal, illuminated, illustrated, immense, imperfect, impossible, impressionist, in_focus, incomplete, industrial, infernal, inner, intense, intimate, intricate, invisible, iridescent, irregular, ivory, jagged, jaguar_spotted, jewel_toned, jeweled, joyful, kinetic, lacquered, lavender, lavish, layered, leafless, leathery, legendary, life_size, light, lilac, liminal, linear, liquid, little, lonely, long, long_exposure, loose, lost, loud, lovely, low, low_contrast, low_poly, lucid, luminous, lunar, lush, macabre, magenta, magic, magical, majestic, malformed, marbled, massive, matte, maximalist, mechanical, medieval, medium, melancholy, menacing, messy, metallic, miniature, minimalist, misty, modern, modernist, molten, monochrome, monstrous, moody, moonlit, mossy, mottled, muddy, muted, mysterious, mystical, naive, narrow, natural, neon, neon_lit, nervous, new, noble, nocturnal, noir, noisy, nonhuman, nostalgic, oily, old, opalescent, open, orange, organic, ornamental, ornate, outer, overgrown, oversized, painted, painterly, pale, pale_blue, pale_green, pale_yellow, panoramic, pastel, patterned, peaceful, peach, pearlescent, pearly, perfect, photorealistic, pink, pitch_black, pixelated, playful, polished, porous, post_apocalyptic, powdery, precise, prehistoric, pretty, prismatic, puffy, purple, quiet, radiant, rainy, random, realistic, rectangular, red, refined, reflective, regal, renaissance, repeating, reptilian, responsive, retro, rich, rim_lit, rococo, romantic, rotten, rough, round, rounded, royal, ruby, ruined, rural, rustic, sacred, sad, sandy, sapphire, satin, saturated, scarlet, sci_fi, scratched, sculptural, seamless, secret, sepia, serene, serpentine, shadowy, shallow, sharp, shattered, sheer, shiny, short, silver, simple, skeletal, slow, small, smoky, smooth, snowy, soft, solarpunk, solitary, soothing, sour, sparkling, speckled, spectral, spiral, square, stained, starlit, static, steampunk, stony, striped, strong, stylized, subtle, sunlit, surreal, symmetrical, tall, tarnished, teal, textured, theatrical, thorny, thunderous, tiny, toxic, translucent, transparent, triangular, tribal, twilight, ultra_detailed, ultra_sharp, ultraviolet, vaporwave, velvet, verdant, vertical, vibrant, victorian, vivid, volcanic, warm, watercolor, waxy, weak, weary, weathered, wet, whimsical, white, wide, wild, windblown, wintery, wispy, wooden, wool, woven, wrinkled, yellow, yellowish, young diff --git a/resources/animals_mythical_creatures.csv b/resources/animals_mythical_creatures.csv index 55a8316..4d27a9d 100644 --- a/resources/animals_mythical_creatures.csv +++ b/resources/animals_mythical_creatures.csv @@ -1 +1 @@ -alligator, alpaca, ant, antelope, ape, badger, bat, bear, beaver, bee, beetle, bird, bison, boar, buffalo, butterfly, camel, cat, cheetah, chicken, cobra, cow, coyote, crab, crocodile, crow, deer, dog, dolphin, donkey, dragon, duck, eagle, elephant, elf, fairy, falcon, fish, flamingo, fox, frog, giraffe, goat, goblin, goose, gorilla, griffin, hamster, hare, hawk, hedgehog, hippo, horse, hydra, jaguar, jellyfish, kangaroo, kitten, koala, kraken, leopard, lion, lizard, llama, lobster, mammoth, mermaid, monkey, moose, mouse, octopus, owl, panda, panther, parrot, penguin, phoenix, pig, pigeon, pony, rabbit, raccoon, rat, raven, rhino, rooster, salmon, shark, sheep, shrimp, skunk, snake, spider, squirrel, swan, tiger, tortoise, troll, turkey, turtle, unicorn, vampire, werewolf, whale, wolf, zebra, zombie +aardvark, adlet, african_elephant, albatross, alicorn, alligator, alpaca, amarok, ammit, anaconda, anansi, angel, angelfish, ankylosaurus, ant, anteater, antelope, ape, aphid, arctic_fox, arctic_wolf, armadillo, aswang, aurochs, axolotl, baboon, badger, baku, bald_eagle, banshee, barracuda, basilisk, bass, bat, bear, beaver, bee, beetle, behemoth, bengal_tiger, bird, bison, blackbird, blue_jay, blue_whale, boar, bobcat, bonobo, brown_bear, budgerigar, buffalo, bull, bunyip, butterfly, cait_sith, calf, camel, canary, capybara, cardinal, caribou, cassowary, cat, caterpillar, centaur, cerberus, chameleon, cheetah, chicken, chimera, chimpanzee, chipmunk, chupacabra, clam, cobra, cockatoo, cockatrice, cod, cougar, cow, coyote, crab, cricket, crocodile, crow, cyclops, deer, deerhound, dhole, dingo, djinn, dodo, dog, dolphin, donkey, doppelganger, dragon, dragonfly, dryad, duck, dwarf, eagle, eel, elephant, elf, emperor_penguin, emu, erlking, fairy, falcon, faun, fenghuang, ferret, finch, firefly, fish, flamingo, flea, fly, fox, frog, gargoyle, gazelle, gecko, gerbil, ghoul, gibbon, giraffe, gnome, goat, goatling, goblin, goldfish, golem, goose, goosefish, gorilla, grasshopper, great_white_shark, green_iguana, greyhound, griffin, grootslang, groundhog, guinea_pig, gull, hamster, harbor_seal, hare, harpy, hawk, hedgehog, hellhound, heron, herring, hippo, hippogriff, honeybee, horse, hound, huldra, hummingbird, husky, hydra, hyena, ibex, ibis, ifrit, iguana, imp, incubus, jackal, jackalope, jaguar, jellyfish, jiangshi, jinn, jorogumo, kangaroo, kappa, kelpie, kingfisher, kitsune, kitten, kiwi, klabautermann, koala, kobold, komodo_dragon, kraken, ladybug, lamia, lemur, leopard, leshy, leviathan, lich, lion, lizard, llama, lobster, loch_ness_monster, lynx, magpie, mammoth, manatee, mandrake, manta_ray, manticore, mare, mastiff, meerkat, melusine, mermaid, minotaur, mole, monarch_butterfly, monkey, moose, moth, mouse, mule, naga, narwhal, newt, nightingale, nixie, nuckelavee, nymph, octopus, ogre, okapi, oni, opossum, orangutan, orc, ork, ostrich, otter, owl, ox, oyster, panda, panther, parrot, peacock, pegasus, pelican, penguin, phoenix, pig, pigeon, plesiosaur, polar_bear, pony, poodle, porcupine, possum, prawn, pterosaur, pufferfish, qilin, quail, rabbit, raccoon, rakshasa, rat, raven, red_fox, red_panda, reindeer, rhino, roc, rooster, rusalka, salamander, salmon, satyr, scallop, sea_lion, sea_serpent, seahorse, seal, selkie, serval, shark, sheep, shrimp, simurgh, siren, skunk, sleipnir, snail, snake, snow_leopard, sphinx, spider, sprite, squirrel, stag, starfish, stegosaurus, stingray, stork, strigoi, succubus, swallow, swan, sylph, tanuki, tapir, tarantula, tengu, termite, thornback_ray, thunderbird, tiger, timber_wolf, toad, tomte, tortoise, toucan, troll, trout, tuna, turkey, turtle, typhon, undine, unicorn, valkyrie, vampire, velociraptor, viper, vodyanoy, vulture, walrus, warthog, wasp, weasel, wendigo, werewolf, whale, white_tiger, wildcat, wildebeest, witch, wizard, wolf, wolpertinger, wombat, woodpecker, worm, wyvern, yak, yaoguai, yeti, yokai, yurei, zebra, zombie diff --git a/resources/camera_composition.csv b/resources/camera_composition.csv new file mode 100644 index 0000000..190cf59 --- /dev/null +++ b/resources/camera_composition.csv @@ -0,0 +1 @@ +abstract_composition,action_shot,aerial_view,ambient_occlusion,anamorphic,aperture_priority,asymmetrical_composition,atmospheric_depth,back_view,background_blur,background_focus,backlit_composition,balanced_composition,bird_eye_view,bokeh,bokeh_background,bottom_up_view,camera_flash,camera_tilt,candid_shot,centered_composition,centered_subject,chaotic_composition,character_close_up,cinematic_angle,cinematic_close_up,cinematic_framing,cinematic_lighting,cinematic_still,clean_background,close_crop,close_focus,close_shot,close_up,cluttered_background,color_grading,composition_study,compositional_balance,compressed_perspective,contact_sheet,contextual_framing,cool_lighting,corner_framing,cropped_composition,cropped_forehead,cropped_subject,crowd_shot,cutaway_shot,deep_depth_of_field,deep_focus,deep_perspective,depth_cue,depth_layering,depth_of_field,detail_shot,diagonal_composition,diagonal_lines,direct_flash,distant_shot,documentary_framing,dolly_zoom,double_exposure,dramatic_angle,dramatic_backlight,dramatic_framing,drone_view,dutch_angle,dutch_composition,dutch_tilt,dynamic_angle,dynamic_composition,editorial_shot,empty_background,environmental_portrait,environmental_shot,establishing_shot,extreme_close_up,extreme_long_shot,extreme_low_angle,extreme_wide_shot,eye_level,eye_level_view,face_focus,face_portrait,fashion_editorial,fast_shutter,fill_the_frame,film_grain,film_still,fine_detail,first_person_view,fisheye,flash_photography,flat_lay,flat_perspective,focal_point,focus_stack,focus_transition,forced_perspective,foreground_blur,foreground_element,foreground_framing,foreground_interest,four_point_perspective,frame_within_frame,framed_subject,freeze_frame,frontal_view,full_body,full_body_shot,full_frame,full_scene,geometric_composition,glossy_photo,golden_ratio,grainy_photo,group_shot,half_body,hand_focus,handheld,head_and_shoulders,hero_shot,high_angle,high_angle_view,high_contrast_lighting,high_fashion_shot,high_key_lighting,horizon_line,horizontal_composition,implied_motion,instant_photo,interior_shot,intimate_framing,isometric_view,landscape_orientation,layered_composition,layered_depth,leading_lines,lens_bokeh,lens_compression,lens_distortion,lens_flare,light_leak,light_rays,long_exposure,long_lens,long_shot,loose_framing,low_angle,low_angle_view,low_key_lighting,low_perspective,macro,macro_shot,magazine_cover,medium_close_up,medium_full_shot,medium_shot,minimal_background,moody_lighting,motion_blur,motion_streaks,natural_framing,natural_light,near_focus,negative_space,negative_space_composition,noir_lighting,object_focus,off_center_composition,one_point_perspective,open_composition,over_the_shoulder,overexposed,overhead_shot,overhead_view,painterly_composition,panning_shot,panoramic_view,partial_view,perspective_depth,photo_portrait,photo_still,photographic_composition,point_of_view,portrait_orientation,portrait_shot,product_shot,profile_shot,profile_view,pull_focus,rack_focus,radial_composition,reaction_shot,rear_view,reflection_shot,rim_lighting,rule_of_thirds,shallow_depth_of_field,shallow_focus,shallow_perspective,side_profile,side_view,silhouette_composition,single_subject,slow_shutter,soft_background,soft_focus,soft_lighting,split_composition,square_composition,studio_lighting,studio_portrait,subject_isolation,subject_tracking,surveillance_view,symmetrical_composition,symmetrical_framing,telephoto,telephoto_compression,three_quarter_view,tight_crop,tight_framing,tilted_frame,top_down_view,tracking_shot,two_point_perspective,underexposed,vanishing_point,vertical_composition,viewpoint,vignette,vintage_photo,wide_angle,wide_composition,wide_lens,wide_shot,worm_eye_view,zoom_blur diff --git a/resources/ing_words.csv b/resources/ing_words.csv index f67c56d..0711cf0 100644 --- a/resources/ing_words.csv +++ b/resources/ing_words.csv @@ -1 +1 @@ -absorbing, abusing, accelerating, accepting, accessing, accompanying, according, accounting, accusing, achieving, aching, acing, acking, acknowledging, acquiring, acting, adapting, adding, addressing, ading, adjusting, admiring, admitting, adopting, advancing, advertising, advising, affecting, ageing, aging, agreeing, ailing, aiming, airing, aking, alarming, aling, alizing, alling, allowing, amarketing, amazing, aming, amreading, amusing, amwriting, analyzing, ancing, angling, aning, announcing, annoying, answering, anticipating, anything, aping, appalling, appealing, appearing, applying, appreciating, approaching, arguing, aring, arming, arranging, arresting, arriving, asing, asking, aspiring, assaulting, assembling, assessing, assisting, assuming, asting, astonishing, astounding, athing, ating, attacking, attempting, attending, attracting, averaging, avoiding, awaiting, awakening, awarding, babysitting, backing, backpacking, baking, balancing, balling, banging, banking, banning, bargaining, barking, bashing, basking, bathing, batting, battling, bbing, bearing, beating, becoming, bedding, begging, beginning, behaving, beijing, being, believing, belonging, bending, benefiting, benefitting, bestselling, betting, bidding, biking, billing, binding, bing, birding, birdwatching, biting, blaming, blasting, blazing, bleeding, blending, blessing, bling, blocking, blogging, blooming, blowing, boarding, boating, bodybuilding, boeing, boiling, bombing, bonding, booking, booming, boosting, boring, borrowing, bottling, bouncing, bowing, bowling, boxing, bragging, brainstorming, branding, braving, breaking, breastfeeding, breathing, breathtaking, breeding, brewing, bridging, briefing, brightening, bring, bringing, broadcasting, browning, browsing, brushing, budding, budgeting, building, bullying, bumping, bunting, burgring, burning, bursting, busting, buying, buzzing, caching, calling, calming, campaigning, camping, canning, canvassing, capping, captivating, capturing, caring, carrying, carving, casting, catching, catering, causing, ceiling, celebrating, chairing, challenging, changing, channeling, channing, chanting, charging, charming, charting, chasing, chatting, cheating, checking, cheering, cheerleading, chewing, chilling, ching, chipping, choking, choosing, chopping, cing, circling, circulating, citing, cking, cladding, claiming, cleaning, cleansing, clearing, clicking, climbing, cling, clipping, closing, clothing, cloudcomputing, clubbing, coaching, coating, coding, collaborating, collecting, coloring, colouring, combating, combining, comforting, coming, commanding, commemorating, commenting, commissioning, committing, commuting, comparing, compelling, competing, complaining, completing, composing, compromising, computing, concerning, conditioning, conducting, conferencing, confirming, conflicting, confusing, congratulating, connecting, conquering, considering, consisting, constructing, consulting, consuming, containing, contemplating, contentmarketing, continuing, contracting, contrasting, contributing, controlling, converting, convincing, cooking, cooling, coordinating, coping, copying, cosplaying, costing, counseling, counselling, counting, coupling, covering, coworking, cracking, crafting, crashing, craving, crawling, creating, creeping, crossing, crowdfunding, cruising, crushing, crying, csrracing, cting, cuddling, curing, curling, cutting, cycling, damaging, dancing, daring, darling, dashing, dating, dazzling, dding, ddling, dealing, debating, debuting, deciding, declaring, declining, decorating, decreasing, deeplearning, defeating, defending, defining, defying, delaying, deleting, delivering, demanding, demonstrating, denying, departing, depending, depicting, deploying, depressing, dering, descending, describing, deserving, designing, designthinking, destroying, detailing, detecting, determining, devastating, developing, dging, digging, digitalmarketing, ding, dining, dipping, directing, disappearing, disappointing, discovering, discussing, disgusting, displaying, disrupting, distributing, disturbing, dividing, diving, djing, dling, docking, documenting, doing, dominating, donating, doodling, doping, doubling, downing, downloading, dprinting, dracing, drafting, dragging, drawing, dreaming, dressing, drifting, drilling, drinking, dripping, driving, drooling, dropping, drowning, drumming, drying, dumping, during, dusting, dwelling, dying, ealing, earning, earring, easing, eating, eding, editing, eing, elding, elearning, electrifying, eliminating, eling, elling, emailmarketing, embarrassing, embracing, emerging, eming, empowering, enabling, enchanting, encing, encouraging, ending, endorsing, enduring, engaging, engineering, engraving, enhancing, ening, enjoying, enlightening, ensuring, entering, entertaining, enting, ering, escaping, establishing, etching, evaluating, evening, everlasting, everything, eving, evolving, ewing, examining, exceeding, exchanging, exciting, excluding, exercising, exhausting, exhibiting, exhilarating, existing, exiting, expanding, expecting, experiencing, experimenting, explaining, exploding, exploring, exporting, exposing, expressing, extending, eyeing, facilitating, facing, fading, failing, falling, fangirling, fanning, farming, fascinating, fasting, featuring, feeding, feeling, fencing, ffing, fielding, fighting, figuring, filing, filling, filming, filmmaking, filtering, financing, finding, fing, finishing, firing, fishing, fitting, fixing, flaming, flashing, flattering, fleeing, fleming, flexing, fling, flipping, flirting, floating, flooding, flooring, flowering, flowing, flushing, flyfishing, flying, focusing, folding, following, fooling, foraging, forcing, forecasting, forgetting, forming, forthcoming, fostering, founding, fracking, framing, freaking, freezing, fridayfeeling, frightening, frosting, frustrating, frying, fting, fueling, fulfilling, functioning, funding, fundraising, fying, gaining, gambling, gaming, gardening, gathering, gating, gazing, gearing, generating, geocaching, gering, getting, gging, ggling, ghting, ghtning, gifting, ging, giving, glamping, globalwarming, glowing, going, golfing, goodmorning, gosling, governing, grabbing, gracing, grading, graduating, grazing, greening, greeting, grieving, grilling, grinding, gring, gripping, grooming, groundbreaking, growing, growthhacking, guarding, guessing, guiding, hacking, hailing, hamstring, handing, handling, handwriting, hanging, happening, happythanksgiving, harding, hardworking, harvesting, hating, hauling, haunting, having, hawking, heading, headlining, healing, healthyeating, healthyliving, hearing, heartbreaking, heartwarming, heating, helping, herring, hiding, highlighting, hiking, hiring, hitting, holding, homecoming, honoring, honouring, hooking, hoping, hopping, horrifying, horseracing, hosting, housing, hovering, howling, hugging, humbling, hunting, hurling, hurting, hydrating, icing, icking, identifying, ignoring, iling, illing, illuminating, illustrating, imaging, imagining, impacting, impending, implementing, imposing, improving, including, incoming, incorporating, increasing, influencing, informing, ing, inging, ining, inking, inning, inspecting, inspiring, installing, insulting, integrating, interacting, interesting, interpreting, interviewing, intimidating, inting, intriguing, introducing, invading, investigating, investing, inviting, involving, iring, irling, ironing, irving, iscoming, ising, issuing, iting, itting, iving, izing, jacking, jamming, jing, jinping, jogging, joining, joking, judging, jumping, justsaying, karting, kayaking, keating, keeping, keting, kettering, kickboxing, kicking, kidding, kidnapping, killing, king, kissing, knitting, knocking, knowing, labeling, lacking, landing, landscaping, lansing, lasting, laughing, launching, laundering, laying, leading, leaking, leaning, learning, leasing, leaving, lending, lettering, letting, leveraging, licensing, licking, lifesaving, lifting, lightening, lighting, lightning, liking, limiting, ling, lingering, lining, linking, listening, listing, living, loading, lobbying, locking, logging, longing, looking, looming, losing, lothing, lounging, loving, lowering, lowing, lurking, lying, machinelearning, machining, maging, mailing, maintaining, making, managing, manning, manufacturing, mapping, marching, marketing, marking, marrying, masking, mastering, matching, mating, mbling, meaning, measuring, meeting, melting, menting, mentioning, mentoring, merchandising, merging, mering, mesmerizing, messaging, messing, migrating, milling, minding, ming, mining, misleading, missing, mitting, mixing, mizing, mming, mocking, modeling, modelling, moderating, molding, mondaymorning, monitoring, morning, motivating, motoring, mounting, mourning, moving, mowing, mping, multitasking, murdering, naming, napping, navigating, nearing, needing, negotiating, neighboring, nering, nesting, networking, ning, nominating, nothing, noticing, noting, nowplaying, nowwatching, nursing, nurturing, observing, occurring, offering, offspring, oilpainting, oling, oming, ongoing, oning, onlinemarketing, onlineshopping, opening, operating, opposing, optimizing, ordering, organising, organizing, orienteering, oring, ounding, ouring, outgoing, outing, outlining, outsourcing, outstanding, overcoming, overflowing, overlooking, overwhelming, owing, owning, pacing, packaging, packing, paddling, painting, pairing, parenting, parking, participating, parting, partnering, partying, passing, patrolling, paving, paying, peacekeeping, peeking, peeling, pending, performing, pering, petting, phishing, photographing, pickering, picking, piercing, piling, ping, pinning, pioneering, piping, pitching, placing, planning, planting, plating, playing, pleasing, pling, plotting, plumbing, plying, poaching, podcasting, pointing, poisoning, poking, policing, polishing, polling, pondering, popping, porting, posing, positioning, posting, pounding, pouring, powering, pping, practicing, practising, praising, praying, preaching, predicting, premiering, preparing, prepping, presenting, preserving, presiding, pressing, pretending, preventing, pricing, printing, printmaking, processing, producing, profiling, programming, progressing, promising, promoting, proofing, proposing, protecting, protesting, prototyping, providing, proving, provoking, prowrestling, pruning, publishing, pudding, pulling, pumping, punching, purchasing, pursuing, pushing, putting, qing, qualifying, questioning, queuing, quitting, quoting, racing, rafting, raging, raining, raising, rallying, ranging, ranking, raping, rapping, rating, raving, reaching, reacting, reading, realising, realizing, reasoning, rebuilding, recalling, receiving, recognising, recognizing, recommending, recording, recovering, recreating, recruiting, recurring, recycling, redding, redefining, rednationrising, reducing, referring, refining, reflecting, refreshing, refusing, regarding, registering, regulating, rehearsing, reigning, relating, relaxing, releasing, reliving, relying, remaining, remembering, reminding, reminiscing, remodeling, removing, rendering, renting, reopening, repairing, repeating, replacing, replying, reporting, reposting, repping, representing, requesting, requiring, rescuing, researching, resisting, respecting, responding, resting, restling, restoring, restructuring, resulting, retaining, rethinking, retiring, retrogaming, returning, retweeting, revealing, reviewing, revising, revisiting, revolving, rewarding, rewatching, riding, riesling, rigging, ring, ringing, ripping, rising, risking, riveting, roaming, roaring, roasting, robbing, rocking, rolling, roofing, rooting, rotating, rounding, routing, rowing, rowling, royalwedding, rubbing, ruining, ruling, running, rushing, rying, sacrificing, safeguarding, sailing, sampling, satisfying, saturdaymorning, saving, saying, scaffolding, scaling, scanning, scheduling, schooling, scoring, scouting, scratching, screaming, screening, screenwriting, scrolling, scubadiving, sculpting, sealing, searching, seasoning, seating, sebring, securing, seeding, seeing, seeking, selecting, selling, sending, sensing, sentencing, senting, separating, sequencing, servicing, serving, setting, settling, sewing, shading, shadowing, shaking, shaming, shaping, sharing, shaving, shedding, shelling, shifting, shing, shining, shipping, shocking, shooting, shopping, shouting, showcasing, showing, shredding, shrinking, shutting, sibling, siding, sighting, sightseeing, signaling, signalling, signing, sing, singing, sinking, sipping, sitting, sizing, sizzling, skateboarding, sketching, skiing, skipping, slaying, sleeping, sliding, sling, slipping, slowing, smashing, smelling, smiling, smoking, smuggling, snacking, snapping, sneaking, snoring, snorkeling, snowboarding, snowing, soaking, soaring, sobbing, socialmediamarketing, solving, something, songwriting, soothing, sorting, sounding, sourcing, spanning, sparkling, sparring, speaking, speeding, spelling, spending, spilling, sping, spinning, spitting, splitting, sponsoring, sporting, spotting, spraying, spreading, spring, springtraining, spying, ssing, stabbing, stacking, staffing, staggering, staging, staining, staking, stalking, stampcollecting, standing, stargazing, staring, starling, starring, starting, starving, stating, staying, stealing, steaming, steering, stephenking, stepping, stering, sterling, sticking, stimulating, sting, stirling, stirring, stitching, stocking, stomping, stopping, storing, storming, storytelling, streaming, strengthening, stressing, stretching, striking, string, stripping, striving, strolling, structuring, struggling, studying, stuffing, stunning, styling, submitting, suffering, suggesting, suing, sunbathing, sundaymorning, supplying, supporting, surfing, suring, surprising, surrounding, surveying, surviving, sustaining, swapping, swearing, sweating, sweeping, swelling, swimming, swing, swinging, switching, tackling, tagging, tailgating, tailoring, taining, taking, talking, tanning, taping, tapping, targeting, tasting, tblightning, teaching, teaming, tearing, teasing, telling, tempting, tending, tering, terrifying, testifying, testing, texting, thanking, thanksgiving, thering, thing, thinking, thismorning, threatening, thrilling, thriving, throwing, ticketing, ticking, timing, ting, tioning, tipping, tiring, tooting, topping, touching, touring, towering, towing, tracing, tracking, trading, trafficking, trailing, training, transferring, transforming, transitioning, translating, transporting, traveling, travelling, treating, trekking, trending, tripping, trolling, trucking, trusting, trying, tting, tubing, tumbling, tuning, turing, turning, tutoring, tweeting, twinning, tying, typing, unboxing, undergoing, undering, underlying, understanding, undertaking, uniting, unlocking, unning, unpacking, unveiling, upcoming, updating, upgrading, uplifting, uploading, uprising, urging, uring, using, utilizing, vanishing, vaping, varying, veling, vending, vening, venting, vering, verything, viewing, viking, ving, violating, vising, visiting, volunteering, voting, waiting, waking, walking, wandering, wanting, warming, warning, washing, wasting, watching, watering, waving, waxing, wearing, weaving, wedding, weeping, weighing, weightlifting, welcoming, welding, wellbeing, welling, wheeling, whipping, whispering, whitening, whopping, widening, wielding, willing, winding, winetasting, wing, winning, wiring, wishing, witnessing, woking, wondering, woodworking, working, worrying, worthing, wrapping, wrecking, wrestling, writing, wyoming, xing, yearling, yelling, ying, yixing, zing, zoning +absconding, abseiling, absorbing, abstracting, abusing, accelerating, accentuating, accepting, accessing, accessorising, accessorizing, accompanying, according, accounting, accusing, achieving, aching, acidwashing, acing, acking, acknowledging, acquiring, acting, activating, adapting, adding, addressing, adjusting, admiring, admitting, adopting, adorning, advancing, advertising, advising, affecting, ageing, aging, agreeing, ailing, aiming, airbrushing, airing, airlifting, alarming, alerting, aligning, alling, allowing, amarketing, amazing, ambienting, ambulating, ambushing, amplifying, amreading, amusing, amwriting, analyzing, ancing, angling, animating, announcing, annoying, answering, anticipating, antiquing, anything, aping, appalling, appealing, appearing, applauding, applying, appraising, appreciating, approaching, arching, arguing, aring, arming, armoring, armouring, arranging, arresting, arriving, ascending, asing, asking, aspiring, assaulting, assembling, assessing, assisting, assuming, asting, astonishing, astounding, atomizing, attacking, attempting, attending, attracting, auditing, authoring, automating, averaging, avoiding, awaiting, awakening, awarding, babysitting, backbending, backfilling, backflipping, backing, backlighting, backlogging, backpacking, backpedaling, backstepping, backstroking, baking, balancing, balling, bandaging, banding, banging, banking, banning, barbecuing, bargaining, barking, barrelling, barricading, bashing, basking, bathing, batting, battling, beachcombing, beading, beaming, bearing, beating, beckoning, becoming, bedazzling, bedding, befriending, begging, beginning, behaving, beheading, being, bejeweling, belaying, believing, belonging, belting, benching, bending, benefiting, benefitting, besieging, bestselling, betting, bicycling, bidding, biking, billing, binding, birding, birdwatching, biting, blackening, blaming, blanching, blasting, blazing, bleeding, blending, blessing, bling, blinking, blistering, blocking, blogging, blooming, blossoming, blousing, blowing, bludgeoning, blurring, boarding, boating, bobbing, bodybuilding, boiling, bombing, bonding, booking, bookmarking, booming, boosting, booting, boring, borrowing, bottling, bouncing, bowing, bowling, boxing, bragging, braiding, brainstorming, branding, brandishing, braving, brawling, breaching, breaking, breastfeeding, breathing, breathtaking, breeding, brewing, bridging, briefing, brightening, bring, bringing, bristling, broadcasting, brocading, brooding, browning, browsing, brushing, brutalizing, bubbling, buckling, budding, budgeting, buffing, building, bullying, bumping, bunting, burning, burrowing, bursting, busting, buying, buzzing, caching, calling, calming, camouflaging, campaigning, camping, canning, canoeing, cantering, canvassing, capering, caping, capping, captivating, capturing, caring, carrying, carving, cascading, casting, cataloging, catching, catering, causing, caving, ceiling, celebrating, chairing, challenging, changing, channeling, channing, chanting, charging, charming, charting, chasing, chatting, cheating, checking, cheering, cheerleading, chewing, chilling, ching, chipping, choking, choosing, chopping, choreographing, chortling, chroming, chuckling, churning, circling, circulating, citing, cladding, claiming, clambering, clasping, classifying, clawing, cleaning, cleansing, clearing, cleaving, clenching, clicking, climbing, cling, clipping, cloaking, closing, clothing, clubbing, coaching, coating, coding, coiling, collaborating, collapsing, collecting, colliding, colorgrading, coloring, colourgrading, colouring, combating, combing, combining, comforting, coming, commanding, commemorating, commenting, commissioning, committing, commuting, comparing, compelling, competing, complaining, completing, composing, compositing, compressing, compromising, computing, concerning, condensing, conditioning, conducting, conferencing, configuring, confirming, conflicting, confusing, congratulating, conjuring, connecting, conquering, considering, consisting, constructing, consulting, consuming, containing, contemplating, contentmarketing, continuing, contorting, contouring, contracting, contrasting, contributing, controlling, converting, convincing, cooing, cooking, cooling, coordinating, coping, copying, cornering, cosplaying, costing, costuming, counseling, counselling, counting, coupling, covering, cowering, coworking, cracking, crafting, crashing, craving, crawling, creating, creeping, crosshatching, crossing, crouching, crowdfunding, crowning, cruising, crumbling, crumpling, crushing, crying, crystallizing, cuddling, curating, curing, curling, curtseying, curving, cutting, cycling, dabbling, damaging, dancing, daring, darling, dashing, databasing, dating, dawdling, daydreaming, dazzling, dealing, debating, debuting, deciding, declaring, declining, decorating, decreasing, defeating, defending, defining, deflecting, deforming, defying, delaying, deleting, delivering, delving, demanding, demonstrating, denoising, denying, departing, depending, depicting, deploying, depressing, desaturating, descending, describing, deserving, designing, designthinking, destroying, detailing, detecting, determining, devastating, developing, devouring, diagnosing, diffusing, digging, digitalmarketing, digitizing, dining, dipping, directing, disappearing, disappointing, disarming, discovering, discussing, disgusting, disintegrating, displaying, disrupting, dissolving, distributing, disturbing, dithering, dividing, diving, djing, docking, documenting, dodging, doing, dominating, donating, doodling, doping, doubling, downing, downloading, dozing, drafting, dragging, draping, drawing, dreaming, dressing, drifting, drilling, drinking, dripping, driving, drooling, dropping, drowning, drumming, drying, dubbing, ducking, dueling, dumping, dunking, dusting, dwelling, dying, earning, easing, eating, editing, electrifying, eliminating, embarrassing, embossing, embracing, emerging, empowering, enabling, enchanting, encouraging, ending, endorsing, enduring, engaging, engineering, engraving, enhancing, enjoying, enlightening, ensuring, entering, entertaining, eroding, erupting, escaping, establishing, etching, evaluating, evaporating, everlasting, everything, evolving, examining, exceeding, exchanging, exciting, excluding, exercising, exhaling, exhausting, exhibiting, exhilarating, existing, exiting, expanding, expecting, experiencing, experimenting, explaining, exploding, exploring, exporting, exposing, expressing, extending, eyeing, facepainting, facilitating, facing, fading, failing, falling, fangirling, fanning, farming, fascinating, fasting, feasting, featuring, feeding, feeling, fencing, fermenting, fidgeting, fielding, fighting, figuring, filing, filling, filming, filmmaking, filtering, financing, finding, finishing, firing, fishing, fissuring, fitting, fixing, flailing, flaming, flanking, flaring, flashing, flattering, fleeing, flexing, flickering, flicking, flinching, fling, flipping, flirting, flitting, floating, flooding, flooring, flowering, flowing, fluffing, flushing, fluttering, flyfishing, flying, foaming, focusing, folding, following, fooling, foraging, forcing, fording, forecasting, foreshortening, forgetting, forging, forming, forthcoming, fostering, founding, fracking, fracturing, framing, fraying, freaking, freezing, frightening, frolicking, frosting, frustrating, frying, fueling, fulfilling, functioning, funding, fundraising, furrowing, gaining, gambling, gaming, gardening, gasping, gathering, gating, gazing, gearing, generating, geocaching, germinating, gesticulating, gesturing, getting, gifting, giggling, gilding, giving, glaciating, glamping, glancing, gliding, glimmering, glinting, glowing, gnashing, goading, going, golfing, governing, grabbing, gracing, grading, graduating, graining, grappling, grasping, grazing, greening, greeting, grieving, grilling, grimacing, grinding, grinning, gripping, groaning, grooming, groundbreaking, growing, growling, guarding, guessing, guiding, gushing, hacking, hailing, haloing, handing, handling, handwriting, hanging, happening, hardworking, harvesting, hatching, hating, hauling, haunting, having, hawking, heading, headlining, healing, healthyeating, healthyliving, hearing, heartbreaking, heartwarming, heating, heaving, helping, hibernating, hiding, highlighting, hiking, hiring, hitting, hoisting, holding, homecoming, honoring, honouring, hooding, hooking, hoping, hopping, horrifying, horseracing, hosting, housing, hovering, howling, hugging, humbling, hunting, hurling, hurting, hurtling, hydrating, icing, identifying, ignoring, illuminating, illustrating, imaging, imagining, impacting, impending, implementing, imposing, improving, including, incoming, incorporating, increasing, indexing, infiltrating, influencing, informing, inhaling, inking, inning, inspecting, inspiring, installing, insulting, integrating, interacting, interesting, interpreting, interviewing, intimidating, intriguing, introducing, invading, investigating, investing, inviting, involving, ironing, issuing, jacking, jamming, jogging, joining, joking, jousting, judging, jumping, karting, kayaking, keeping, keyframing, kickboxing, kicking, kidding, kidnapping, killing, kissing, kitbashing, kneeling, knitting, knocking, knowing, labeling, lacing, lacking, landing, landscaping, lasting, laughing, launching, laundering, layering, laying, leading, leaking, leaning, leaping, learning, leasing, leaving, lending, lettering, letting, leveraging, levitating, licensing, licking, lifesaving, lifting, lightening, lighting, lightning, liking, limiting, lingering, lining, linking, listening, listing, living, loading, lobbying, locking, logging, longing, looking, looming, looping, losing, lounging, loving, lowering, lunging, lurking, lying, machining, mailing, maintaining, making, managing, manning, manufacturing, mapping, marching, marketing, marking, marrying, masking, mastering, matching, mating, matting, meaning, measuring, meditating, meeting, melting, mentioning, mentoring, merchandising, merging, mesmerizing, messaging, messing, migrating, milling, minding, mining, misleading, missing, misting, mixing, mocking, modeling, modelling, moderating, molding, monitoring, morphing, motivating, motoring, mountaineering, mounting, mourning, moving, mowing, multitasking, murdering, naming, napping, navigating, nearing, needing, negotiating, neighboring, nesting, networking, nodding, nominating, noticing, noting, nursing, nurturing, observing, occurring, offering, oilpainting, ongoing, oozing, opening, operating, opposing, optimizing, ordering, organising, organizing, orienteering, outgoing, outing, outlining, outsourcing, outstanding, overcoming, overflowing, overlanding, overlooking, overwhelming, owing, owning, oxidizing, pacing, packaging, packing, paddling, painting, pairing, parachuting, parenting, parking, parkouring, parrying, participating, parting, partnering, partying, passing, patrolling, paving, paying, peacekeeping, peeking, peeling, pending, perching, performing, petting, phishing, photographing, picking, piercing, piling, pillaging, pinning, pioneering, piping, pirouetting, pitching, pixelating, placing, planning, planting, plating, playing, pleasing, plotting, plumbing, plying, poaching, podcasting, pointing, poisoning, poking, policing, polishing, polling, pondering, popping, porting, posing, positioning, posting, pouncing, pounding, pouring, pouting, powering, practicing, practising, praising, praying, preaching, predicting, premiering, preparing, prepping, presenting, preserving, presiding, pressing, pretending, preventing, pricing, printing, printmaking, processing, producing, profiling, programming, progressing, promising, promoting, prompting, proofing, proposing, protecting, protesting, prototyping, providing, proving, provoking, prowling, prowrestling, pruning, publishing, pulling, pumping, punching, purchasing, pursuing, pushing, putting, qualifying, questioning, queuing, quitting, quoting, racing, radiating, rafting, raging, raining, raising, rallying, ranging, ranking, raping, rappelling, rapping, rasterizing, rating, raving, reaching, reacting, reading, realising, realizing, reasoning, rebalancing, rebuilding, recalling, receiving, reclining, recognising, recognizing, recoiling, recoloring, recolouring, recommending, reconditioning, recording, recovering, recreating, recruiting, recurring, recycling, redefining, reducing, reenacting, referring, refining, reflecting, reframing, refreshing, refusing, regarding, registering, regulating, rehearsing, reigning, relating, relaxing, releasing, relighting, reliving, relying, remaining, remastering, remembering, reminding, reminiscing, remodeling, removing, rendering, renting, reopening, repairing, repeating, replacing, replying, reporting, reposting, repping, representing, requesting, requiring, rerolling, resampling, rescaling, rescuing, researching, reshading, resisting, respecting, responding, resting, restoring, restructuring, resulting, retaining, rethinking, retiring, retouching, retreating, returning, retweeting, revealing, reviewing, revising, revisiting, revolving, rewarding, rewatching, ricocheting, riding, rigging, ringing, ripping, rippling, rising, risking, riveting, roaming, roaring, roasting, robbing, robing, rocking, rolling, roofing, rooting, rotating, rotoscoping, rounding, routing, rowing, rubbing, ruining, ruling, running, rushing, sacrificing, safeguarding, sailing, saluting, sampling, satisfying, saturating, saving, saying, scaffolding, scaling, scanning, scheduling, schooling, scoring, scouting, scowling, scratching, screaming, screening, screenwriting, scrolling, scubadiving, sculpting, sealing, searching, seasoning, seating, securing, seeding, seeing, seeking, seeping, selecting, selling, sending, sensing, sentencing, separating, sequencing, servicing, serving, setting, settling, sewing, shading, shadowcasting, shadowing, shaking, shaming, shaping, sharing, sharpening, shaving, shedding, shelling, shifting, shimmering, shining, shipping, shocking, shooting, shopping, shouting, showcasing, showing, shredding, shrieking, shrinking, shutting, siding, sighting, sightseeing, signaling, signalling, signing, singing, sinking, sipping, sitting, sizing, sizzling, skateboarding, sketching, skiing, skipping, slashing, slaying, sledding, sleeping, sliding, slipping, slouching, slowing, smashing, smelling, smiling, smirking, smoking, smoldering, smuggling, snacking, snapping, snarling, sneaking, sneering, snoring, snorkeling, snowboarding, snowing, soaking, soaring, sobbing, solving, songwriting, soothing, sorting, sounding, sourcing, spanning, sparkling, sparring, speaking, speeding, spelling, spelunking, spending, spilling, spinning, spitting, splitting, sponsoring, sporting, spotting, spraying, spreading, sprinting, spying, squatting, stabbing, stacking, staffing, staggering, staging, staining, staking, stalking, stampcollecting, standing, stargazing, staring, starring, starting, starving, stating, staying, stealing, steaming, steering, stepping, sticking, stimulating, stippling, stirring, stitching, stocking, stomping, stopping, storing, storming, storyboarding, storytelling, straddling, strafing, strangling, streaming, strengthening, stressing, stretching, striding, striking, stripping, striving, strolling, structuring, struggling, studying, stuffing, stunning, styling, submitting, subtitling, suffering, suggesting, suing, suiting, sunbathing, supersampling, supplying, supporting, surfing, surprising, surrendering, surrounding, surveying, surviving, sustaining, swapping, swashbuckling, swearing, sweating, sweeping, swelling, swimming, swing, swinging, switching, swooping, tackling, tagging, tailgating, tailoring, taking, talking, tanning, taping, tapping, targeting, tasting, tattooing, teaching, teaming, tearing, teasing, telling, tempting, tending, terrifying, testifying, testing, texting, texturing, thanking, thawing, thinking, threatening, thrilling, thriving, throwing, ticketing, ticking, timing, tipping, tiptoeing, tiring, tooting, topping, touching, touring, towering, towing, tracing, tracking, trading, trafficking, trailing, trailrunning, training, transferring, transforming, transitioning, translating, transporting, traveling, travelling, treating, trekking, trembling, trending, tripping, trolling, trucking, trusting, trying, tubing, tumbling, tuning, turning, tutoring, tweeting, twinkling, twinning, twirling, tying, typing, unboxing, undergoing, underlying, underpainting, understanding, undertaking, uniting, unlocking, unpacking, unveiling, upcoming, updating, upgrading, uplifting, uploading, uprising, upscaling, urging, using, utilizing, vanishing, vaping, varnishing, varying, vaulting, vectorizing, veiling, vending, venting, viewing, vignetteing, violating, vising, visiting, volunteering, voting, voxelizing, wading, waiting, waking, walking, wandering, wanting, warming, warning, washing, wasting, watching, watering, waving, waxing, wearing, weathering, weaving, wedding, weeping, weighing, weightlifting, welcoming, welding, wheeling, whipping, whispering, whitening, whopping, widening, wielding, willing, wilting, winding, windsurfing, winetasting, winning, wiring, wishing, witnessing, woking, wondering, woodworking, working, worrying, wrapping, wreathing, wrecking, wrestling, writhing, writing, yawning, yearling, yelling, zoning diff --git a/resources/objects.csv b/resources/objects.csv index 8df965a..dd0492e 100644 --- a/resources/objects.csv +++ b/resources/objects.csv @@ -1 +1 @@ -album, armor, armour, bag, ball, basket, battery, bed, belt, bicycle, bike, blade, blanket, boat, book, boot, bottle, bowl, box, bracelet, brush, bucket, button, camera, candle, canvas, car, card, carpet, chair, clock, cloth, coin, comb, cord, cup, curtain, desk, document, door, dress, envelope, fabric, fan, flag, flower, folder, fork, frame, glass, glove, guitar, hammer, handle, hat, helmet, jacket, jar, key, keyboard, knife, lamp, laptop, letter, lock, map, mask, mirror, necklace, needle, notebook, paint, painting, pan, paper, pen, pencil, phone, photograph, pillow, plate, poster, ring, rope, rug, scissors, screen, shelf, shirt, shoe, sign, spoon, stone, suitcase, table, tablet, ticket, tool, towel, toy, umbrella, vase, wallet, watch, wheel, window, wire, wood +ac_unit, accent_chair, access_card, accordion, acrylic_paint, action_figure, adapter, address_book, adhesive_tape, aerosol_can, air_conditioner, air_filter, air_fryer, air_purifier, airbrush, airplane, alarm_clock, album, allen_key, altar, ambulance, ammunition_box, amplifier, amulet, anchor, ankle_boot, ankle_monitor, anklet, antenna, anvil, apron, aquarium, archery_bow, area_rug, armchair, armor, armor_plate, armour, armrest, arrow, artbook, ashtray, awning, axe, baby_bottle, baby_carriage, baby_gate, baby_monitor, backboard, backpack, backpack_strap, badge, badge_holder, bag, baggage_tag, baking_dish, baking_pan, baking_sheet, baking_tray, balaclava, balance_scale, ball, ball_peen_hammer, balloon, ballot_box, ballpoint_pen, baluster, bandage, bandana, banjo, banknote, banner, banner_pole, bar_cart, bar_stool, barbecue_grill, barbell, barcode, barometer, barrel, baseball_bat, baseball_cap, baseball_glove, basin, basket, basketball_hoop, bass_guitar, bat, bath_mat, bath_towel, bathrobe, bathrobe_belt, bathroom_mirror, bathroom_scale, bathtub, baton, battery, battle_axe, bayonet, beach_ball, beach_chair, beach_towel, bead, bead_necklace, beaker, beanbag, beanie, bed, bed_frame, bedpost, bedroll, bedsheet, bedside_table, beer_bottle, beer_can, beer_glass, bell, bellows, belt, bench, bench_seat, bento_box, bicycle, bicycle_bell, bicycle_chain, bicycle_frame, bicycle_helmet, bicycle_lock, bicycle_pump, bike, bike_lock, billfold, bin, binder, binder_clip, binoculars, bird_feeder, birdcage, birth_certificate, birthday_card, biscuit_tin, black_light, blackboard, blade, blanket, blanket_chest, blanket_roll, blender, blinds, block, blood_bag, blowtorch, blueprint, bluetooth_speaker, board, boarding_pass, boat, bodysuit, boiler, bolt, bolt_cutter, book, book_bag, book_cover, bookend, bookmark, bookmark_ribbon, bookshelf, bookshelf_ladder, boombox, boomerang, boot, boot_print, bootlace, bottle, bottle_brush, bottle_cap, bottle_label, bottle_opener, bouquet, bow, bowl, bowling_pin, box, boxing_glove, bracelet, bracelet_chain, bracelet_charm, brake, bread_basket, bread_box, bread_knife, breadboard, breastplate, briefcase, briefcase_handle, broiler, brooch, brooch_pin, broom, brush, brush_pen, bubble_level, bubble_wrap, bucket, bucket_hat, buckle, bug_net, bullet, bulletin_board, bullhorn, bumper, bunk_bed, bunsen_burner, bunting_flag, buoy, burner, bus, bus_stop_sign, butter_dish, butter_knife, button, button_panel, cabinet, cabinet_door, cabinet_handle, cable, cable_box, cable_tie, cactus_pot, cake_box, cake_pan, cake_stand, calculator, caliper, calligraphy_brush, camcorder, camera, camera_bag, camera_flash, camera_lens, camera_strap, camisole, camping_stove, can, can_opener, candle, candle_holder, candle_lantern, candle_snuffer, candy_cane, candy_wrapper, canoe, canteen, canvas, canvas_bag, cap, cape, car, car_battery, car_door, car_key, car_mirror, car_seat, car_wheel, card, card_deck, cardboard_box, cardigan, cargo_crate, cargo_net, carpet, carpet_beater, carpet_runner, carriage, cart, carving_fork, cash_register, casserole_dish, cassette, cassette_player, cast, cast_iron_pan, cauldron, cd_case, ceiling_fan, ceiling_light, ceiling_tile, cell_phone, cello, ceramic_bowl, ceramic_plate, cereal_box, chain, chainmail, chair, chaise_lounge, chalice, chalk, chalkboard, champagne_bottle, champagne_flute, charcoal_pencil, charger, charger_cable, charging_cable, charging_station, charm, charm_bracelet, cheese_board, cheese_grater, chef_hat, chef_knife, chemical_bottle, chess_clock, chess_piece, chess_set, chessboard, chest, chip, chisel, chisel_tip, chopsticks, christmas_ornament, cigar_box, cigarette_case, circuit_board, clamp, clay_pot, cleaning_brush, cleaning_cloth, cleaver, climbing_harness, climbing_rope, clipboard, clipboard_chart, cloak, cloak_pin, clock, clock_face, closet, cloth, clothes_hanger, clothesline, clothespin, clutch, coat, coat_button, coat_hanger, coat_rack, cocktail_glass, coffee_cup, coffee_filter, coffee_maker, coffee_mug, coffee_pot, coffee_table, coin, coin_purse, coin_stack, colander, collar, cologne_bottle, colored_pencil, comb, comb_case, comic_book, comic_panel, compact_disc, compact_mirror, compass, compass_tool, computer_chip, computer_mouse, conduit, console_table, controller, cookie_cutter, cookie_jar, cooking_pot, cooking_spoon, cooler, coping_saw, copper_pipe, copper_wire, cord, cord_reel, corduroy_cap, cork, corkscrew, corset, cosmetic_bag, costume_mask, cotton_swab, coupon, cowboy_hat, cpu, crate, crayon, credit_card, credit_card_reader, crib, crib_mobile, crossbow, crowbar, crown, crutch, crystal, crystal_ball, crystal_shard, cufflink, cup, cupboard, curtain, curtain_hook, curtain_ring, curtain_rod, cushion, cutting_board, cymbal, dagger, dart, dartboard, data_cable, data_drive, daybed, deadbolt, debit_card, decanter, denim_jacket, dental_mirror, desk, desk_chair, desk_lamp, desk_organizer, desktop_computer, detergent_bottle, diaper, diary, dice, dice_bag, digital_camera, digital_clock, dining_table, dinner_plate, dish, dish_rack, display_case, diving_mask, document, dog_collar, dog_tag, doll, dollhouse, door, door_bell, door_handle, door_knob, door_mat, doorbell_button, doorknob, doormat, drafting_table, drain_pipe, drawer, drawer_handle, drawing_board, drawing_tablet, dress, dress_form, dresser, drill, drill_bit, drinking_glass, drinking_straw, drone, drop_cloth, dropper, drum, drum_machine, drum_pad, drumstick, duct_tape, dumbbell, dustpan, duvet, earbud_case, earbuds, earplug, earring, easel, egg_timer, elbow_pad, electric_drill, electric_guitar, electric_kettle, electric_lantern, electric_razor, electrical_box, enamel_pin, end_table, engine, engine_block, engine_part, envelope, envelope_seal, eraser, espresso_machine, extension_cord, external_drive, eye_patch, eyeglasses, fabric, fabric_roll, face_mask, face_shield, fan, faucet, feather, feather_duster, feather_quill, ferris_wheel, fidget_spinner, file, filing_cabinet, film_camera, film_canister, fire_alarm, fire_axe, fire_extinguisher, fire_hydrant, fire_pit, fire_poker, firewood, first_aid_kit, fish_hook, fishing_hook, fishing_net, fishing_rod, fishing_tackle, flag, flagpole, flashlight, flask, flip_flop, floor_lamp, floor_tile, flower, flower_pot, flower_vase, flute, foam_block, folder, folding_chair, folding_table, food_bowl, food_carton, football, football_helmet, footlocker, footrest, footstool, forceps, fork, forklift, fountain, fountain_pen, fountain_pen_nib, frame, freezer, fridge_magnet, frying_pan, fuel_can, funnel, fuse, futon, game_controller, gaming_keyboard, garden_glove, garden_hose, garden_pot, garden_shears, garment_rack, gas_can, gas_lantern, gas_mask, gate, gauntlet, gauze, gear, gift_bag, gift_box, gift_ribbon, glass, glass_bead, glass_bottle, glass_case, glass_jar, glass_panel, glass_tube, globe, glove, glove_box, glow_stick, glue_stick, goggles, gold_coin, golf_club, gown, graphics_card, grappling_hook, grater, grenade, grill, grip_tape, grocery_bag, guitar, guitar_pick, hacksaw, hair_band, hair_brush, hair_clip, hair_dryer, hair_tie, hairpin, hammer, hamper, hand_drill, hand_fan, hand_lantern, hand_mirror, hand_saw, hand_truck, handbag, handcuffs, handle, handrail, hanger, hard_drive, hard_hat, harmonica, harp, hat, hatbox, hay_bale, headband, headboard, headlight, headphones, headset, heater, helmet, helmet_camera, helmet_visor, hex_key, high_heel, highlighter, hiking_boot, hiking_pack, hinge, hockey_mask, hockey_stick, holster, hoodie, hook, hoop, horseshoe, hose, hot_plate, hourglass, ice_bucket, ice_cream_cone, ice_cube_tray, ice_pick, ice_skate, ice_tray, identification_card, idol, incense, incense_burner, index_card, ink_bottle, ink_pen, inkwell, iron, ironing_board, jack, jacket, jackhammer, jam_jar, jar, jewelry_box, jigsaw, jigsaw_puzzle, joystick, joystick_handle, juice_box, jump_rope, kayak, kettle, kettle_bell, key, key_card, keyboard, keyboard_instrument, keychain, keyhole, keyring, kimono, kitchen_scale, kitchen_timer, knee_pad, kneepad, knife, knitting_needle, lab_coat, lace, lace_glove, ladle, laminated_card, lamp, lamp_post, lamp_shade, lantern, lantern_hook, lantern_pole, lanyard, laptop, laptop_bag, laser_pointer, laser_sight, laundry_basket, laundry_detergent, laundry_pin, leash, leather_bag, leather_belt, leather_boot, lectern, led_light, leggings, lens_cap, letter, letter_opener, letter_tray, level, license_plate, life_jacket, life_preserver, light_bulb, light_switch, lighter, linen, lipstick, loafer, lock, locker, longsword, loveseat, luggage_tag, lunch_bag, lunchbox, lute, machine_part, magic_lantern, magnet, magnifier, magnifying_glass, mail_slot, mailbag, mailbox, makeup_brush, makeup_palette, mallet, mandolin, mannequin, map, marble, marbles, marker, marker_pen, mask, masking_tape, mattress, measuring_cup, measuring_spoon, measuring_tape, medal, medical_mask, medicine_bottle, medicine_kit, megaphone, memory_card, metal_box, metal_chain, metal_pipe, metal_plate, metronome, microphone, microphone_stand, microscope, military_helmet, milk_carton, mini_fridge, mirror, mirror_frame, mitten, mixing_bowl, mixing_spoon, mobile_phone, model_airplane, model_car, modem, monitor, monitor_arm, mop, mop_bucket, mortar, mortar_and_pestle, motorcycle, motorcycle_helmet, mouse_cord, mouse_trap, mousepad, mouthpiece, movie_camera, mug, mug_handle, music_box, music_stand, nail, nail_file, name_tag, napkin, necklace, necktie, needle, neon_sign, nesting_box, net, nightstand, notebook, notebook_paper, notepad, nut, oar, office_chair, oil_can, oil_lamp, oil_paint, oil_pastel, operating_table, ornament, ottoman, outdoor_lamp, oven_mitt, pad, paddle, padlock, paint, paint_brush, paint_bucket, paint_can, paint_palette, paint_roller, paint_tube, paintbrush, painting, palette, pallet, pan, paper, paper_bag, paper_clip, paper_cup, paper_lantern, paper_plane, paper_towel, paper_weight, parachute, parcel, park_bench, parking_meter, party_hat, passport, pastel, pastry_brush, patch, pegboard, pen, pen_holder, pen_nib, pencil, pencil_case, pencil_sharpener, pendant, pepper_mill, perfume, perfume_bottle, petri_dish, phone, phone_case, photo_album, photo_frame, photograph, piano, piano_key, pickaxe, picnic_basket, picture_frame, picture_hook, pill_bottle, pillow, pin, pinboard, pipe, pipe_wrench, pipette, pitcher, plane, plant_stand, plastic_bag, plastic_bottle, plastic_container, plate, play_card, pliers, pocket_knife, pocket_watch, polaroid, poncho, pool_float, porcelain_cup, postbox, postcard, poster, pot, pouf, powder_box, power_bank, power_cable, power_strip, prayer_beads, pressure_cooker, pressure_gauge, price_tag, printer, projector, propeller, punch_card, purse, push_pin, puzzle_piece, quill, quilt, racket, radio, railing, rain_boot, raincoat, rake, receipt, record, recorder, refrigerator, remote_control, respirator, ribbon, ring, ring_box, rivet, road_sign, rocking_chair, roller_skate, rolling_pin, room_divider, rope, rope_ladder, router, rubber_band, rubber_duck, rug, ruler, running_shoe, saddle, saddle_bag, safety_cone, safety_goggles, safety_harness, safety_pin, sail, sailboat, salt_cellar, salt_shaker, sand_bucket, sandal, sandpaper, sash, satellite_dish, sauce_bottle, saucepan, saw, saxophone, scalpel, scarf, scarf_pin, scarf_ring, school_bag, school_desk, scissors, scooter, scooter_helmet, screen, screen_door, screw, screwdriver, scroll, sd_card, sea_shell, sewing_machine, sewing_needle, sewing_thread, shadow_box, shampoo_bottle, shaving_brush, shaving_razor, shawl, shelf, shield, shipping_label, shirt, shoe, shoe_lace, shoebox, shop_sign, shopping_bag, shopping_cart, shoulder_bag, shovel, shower_cap, shower_curtain, shower_head, shutter, side_mirror, side_table, sign, signal_lamp, silk_scarf, silver_spoon, sink, skateboard, skateboard_wheel, sketch_pad, sketchbook, ski, skillet, sled, sleep_mask, sleeper_sofa, sleeping_bag, slide_projector, slipper, smartwatch, sneaker, snow_globe, snow_shovel, snowboard, soap_bar, soap_bottle, soap_dish, soap_dispenser, soccer_ball, soccer_cleat, sock, socket, socket_wrench, sofa, soldering_iron, soup_bowl, spade, spark_plug, spatula, speaker, speaker_cable, spear, spellbook, spice_bottle, spice_jar, spice_rack, spinning_wheel, sponge, spoon, spoon_rest, sports_bag, spray_bottle, spray_paint, squeegee, staff, stained_glass, stamp, standing_lamp, staple, staple_gun, stapler, statue, steel_beam, steering_column, steering_wheel, step_ladder, stethoscope, sticky_note, stone, stone_tablet, stool, stop_sign, storage_bin, storage_box, strainer, straw_hat, street_lamp, street_sign, string, string_light, stylus, submarine, sugar_bowl, suitcase, suitcase_tag, sundial, sunglasses, surfboard, surgical_mask, swatch, sweater, switch, sword, sword_sheath, syringe, table, table_lamp, tablecloth, tablet, tablet_pen, tackle_box, tambourine, tape, tape_measure, tarpaulin, tea_bag, tea_cup, tea_kettle, tea_set, tea_strainer, teacup, teapot, teddy_bear, telescope, television, tennis_ball, tennis_racket, tent, test_tube, thermal_bottle, thermometer, thermos, throw_pillow, thumbtack, ticket, ticket_stub, tie, tile, tire, tissue_box, toaster, toilet_brush, toilet_paper, tool, toolbelt, toolbox, toothbrush, toothpaste, top_hat, torch, tote_bag, touchscreen, towel, towel_rack, toy, toy_block, toy_car, toy_train, tracksuit, tractor, traffic_cone, traffic_light, traffic_sign, train, tram, trash_bag, trash_can, travel_mug, tray, treasure_chest, trench_coat, trench_knife, tripod, trophy, trousers, trowel, truck, trumpet, tuba, tuning_fork, turntable, tv_remote, typewriter, umbrella, umbrella_stand, urn, usb_cable, utility_knife, vacuum_cleaner, vacuum_hose, van, vanity, vase, vest, video_camera, vinyl_record, violin, vise, vr_headset, wagon, walkie_talkie, walking_cane, wall_clock, wall_sconce, wallet, wand, wardrobe, washcloth, washer, washing_machine, watch, water_bottle, water_filter, water_gun, watercolor_paint, watering_can, wax_seal, weather_vane, webcam, welding_mask, wheel, wheelbarrow, wheelchair, whisk, whistle, wind_chime, window, window_blind, windshield, wine_bottle, wine_glass, wine_opener, wire, wire_brush, wire_cutter, wire_spool, wok, wood, wooden_box, wooden_crate, wooden_spoon, wool_blanket, workbench, wrapping_paper, wrench, wrist_guard, wristband, wristwatch, xylophone, yarn, yarn_ball, yoga_mat, zipper, zipper_pull diff --git a/resources/verbs.csv b/resources/verbs.csv index 4fb4181..307f003 100644 --- a/resources/verbs.csv +++ b/resources/verbs.csv @@ -1 +1 @@ -accept, add, admire, advise, agree, aim, allow, answer, appear, apply, arrive, ask, bake, balance, believe, build, burn, call, carry, change, chase, choose, clean, climb, close, collect, combine, compare, connect, cook, create, cry, dance, deliver, destroy, draw, drink, drive, drop, eat, edit, enter, escape, explore, fall, feed, fight, find, fly, follow, generate, give, grab, grow, guide, help, hide, hold, imagine, improve, include, join, jump, keep, kick, kiss, laugh, learn, leave, lift, listen, load, look, make, mix, move, open, paint, pass, pick, place, play, pull, push, read, remove, repair, replace, ride, run, save, search, see, select, send, shape, share, shoot, show, sing, sit, sleep, smile, sort, speak, split, stand, start, stop, stretch, swim, take, talk, teach, test, throw, touch, train, travel, try, turn, update, use, visit, wait, walk, wash, watch, wear, win, write +abandon, absorb, absorb_light, accelerate, accept, accessorize, accompany, accuse, achieve, act, act_out, activate, adapt, add, add_detail, add_noise, adjust, adjust_lighting, adjust_pose, adjust_scale, adjust_shadows, admire, adopt, advance, advertise, advise, affect, age, agree, aim, aim_at, alert, align, align_subject, allow, alter, alternate_pose, amplify_color, anchor_object, angle_camera, animate, animate_scene, announce, answer, anticipate, appear, applaud, apply, apply_filter, approach, arch_back, argue, arm, arrange, arrange_objects, arrange_scene, arrest, arrive, ask, assemble, assess, assist, attach, attach_layer, attach_object, attack, attempt, attend, attract, avoid, avoid_gaze, awake, awaken, award, back_away, backflip, backlight, backpedal, backstroke, bake, balance, balance_composition, barricade, bask, bathe, battle, beam, beat, beckon, believe, bend, bend_forward, bend_knee, benefit, bind, bite, blame, blast, blaze, bleed, blend, blend_colors, bless, blink, block, block_path, bloom, blow, blow_kiss, blur, blur_background, blur_edges, board, boil, bomb, bond, book, boost, borrow, bounce, bow, braid, brandish, break, break_apart, breathe, brighten, brighten_highlights, brighten_scene, bring_forward, broadcast, bruise, brush, brush_hair, brush_surface, bubble, buckle, build, build_scene, burn, bury, call, capture_moment, carry, carve, cast, cast_shadow, cast_spell, catch, celebrate, challenge, change, change_angle, change_expression, change_outfit, charge, charge_forward, charm, chase, chase_target, chat, cheat, check_map, check_phone, cheer, chew, chill, chip, choke, choose, choose_color, circle, cite, clasp, claw, clean, clean_room, clean_surface, clear, click, climb, climb_ladder, climb_over, cling, clip, cloak, close, close_door, close_eyes, clothe, coach, coat, collect, collect_item, collect_samples, color, color_grade, colour, combine, combine_layers, comfort, command, comment, commit, commute, compare, compare_versions, compete, complain, compose, compose_image, compose_prompt, conceal, conceal_face, conceal_object, condition, confirm, confront, confuse, conjure, connect, connect_cable, connect_wire, consider, construct, consult, consume, contain, contemplate, contract, contribute, control, convert, convince, cook, cook_food, cook_meal, copy, copy_pose, copy_style, correct, correct_color, cover, cover_body, cover_face, crack, crack_open, craft, craft_item, crash, crave, crawl, crawl_forward, create, create_scene, creep, crop_image, cross, cross_arms, cross_leg, cross_street, crouch, crouch_down, cruise, crush, crush_object, cry, cuddle, cuddle_pet, curl, curl_hair, curve, cut, cut_paper, cut_rope, dabble, damage, damage_surface, dance, dare, dash, date, dazzle, debate, declare, decline, decorate, decorate_object, decorate_room, decrease, deepen_shadow, defeat, defend, define, define_edges, deflect, delay, delete, delete_layer, delete_object, deliver, deliver_package, demonstrate, deny, depict_action, deploy, descend, descend_stairs, describe, describe_scene, design, design_character, design_outfit, destroy, detect, detect_edge, detect_motion, determine, develop, dig, dim_light, dine, dine_out, dip, direct, direct_gaze, direct_light, disappear, discover, discuss, display, display_item, disrupt, distribute, disturb, dive, document, dodge, dodge_attack, dodge_left, dodge_right, dominate, donate, doodle, double, download, draft, drag, draw, draw_line, draw_weapon, dream, dream_up, dress, dress_up, drift, drift_away, drill, drill_hole, drink, drink_water, drip, drive, drive_car, drop, drop_item, drop_shadow, drown, dry_clothes, duel, dump, dust, dwell, earn, ease, eat, edit, edit_image, embrace, emerge, empower, enable, enchant, encourage, endure, engage, engrave, enhance, enhance_detail, enjoy, enter, enter_room, entertain, erase_mark, escape, escape_scene, establish, etch, evaluate, exaggerate_pose, examine, examine_object, exceed, exchange, exchange_item, excite, exercise, exhaust, expand, expand_canvas, expect, experiment, explain, explode, explore, explore_area, export, export_image, expose, expose_surface, express, express_emotion, extend, extend_arm, extend_leg, face, face_camera, face_left, face_right, fade, fade_out, fail, fall, fall_asleep, fall_back, fall_down, farm, fast, fasten_belt, feature, feed, feed_animal, feel, fence, fetch, fight, fight_back, file, fill, fill_frame, fill_space, film, film_scene, filter, filter_light, finance, find, find_path, finish, finish_task, fire, fire_weapon, fix, fix_detail, fix_pose, flame, flame_up, flash, flash_light, flatten, flatten_image, flee, flee_scene, flex, flip, flip_hair, flip_page, flirt, float, float_above, flood, flood_area, fly, fly_over, focus_camera, fold, fold_cloth, fold_paper, follow, follow_path, follow_subject, forecast, forge, forge_metal, forgive, form, form_shape, foster, found, fracture, fracture_surface, frame, frame_face, frame_subject, freeze, freeze_motion, frighten, frighten_viewer, fry, fuel, fulfill, fund, gain, gamble, gather, gather_items, gather_light, gaze, gaze_at, gear, generate, generate_prompt, germinate, gesture, gild, give, glance, glide, glide_forward, glimmer, glow, glow_softly, grab, grab_edge, grab_object, grade, graduate, graze, greet, grieve, grind, grip, grip_handle, groom, groom_hair, grow, guard, guard_door, guess, guide, guide_viewer, hail, hand, handle, hang, hang_lantern, hang_picture, happen, harvest, hate, haul, haul_crate, haunt, heal, heal_wound, heat, heat_metal, help, hide, hide_face, hide_object, highlight, highlight_edge, hike, hire, hit, hold, hold_item, hold_weapon, hook, hop, hope, hover, hover_above, howl, hug, hug_person, hunt, hunt_prey, hurt, hydrate, hydrate_skin, identify, identify_object, ignore, illuminate, illuminate_room, illustrate, illustrate_story, imagine, impact, implement, impose, improve, improve_detail, improve_prompt, include, include_object, incorporate, increase, increase_contrast, influence, influence_mood, inform, inform_viewer, inspect, inspect_object, inspire, install, install_part, insult, integrate, interact, interact_with_object, interact_with_scene, interpret, interpret_symbol, interview, interview_subject, intimidate, introduce, introduce_character, invade, invest, investigate, investigate_area, invite, invite_viewer, involve, isolate_subject, issue, jam, jog, join, join_group, join_hands, joke, judge, jump, jump_down, jump_forward, jump_over, keep, keep_distance, kick, kick_ball, kidnap, kiss, kiss_cheek, kneel_down, knit, knit_scarf, knock, knock_door, know, label, label_layer, lack, land, landscape, landscape_scene, last, laugh, launch, launch_projectile, launder, lay, lay_down, layer_colors, lead_eye, leak, lean, lean_back, lean_forward, lean_in, learn, lease, leave, leave_room, lend, lend_hand, let, leverage, lick, lift, lift_head, lift_object, light_fire, light_scene, limit, line, line_up, linger, link, list, listen, listen_to, live, load, load_file, load_model, lobby, lock, lock_door, lock_focus, log, long, look, look_at, look_away, look_down, look_left, look_right, look_up, lounge, lower_arm, lower_head, lurk, maintain, make, make_contact, make_pose, manage, manufacture, map, march, mark, mark_area, marry, mask, mask_area, mask_face, master, match, match_color, measure, measure_distance, melt, melt_ice, mention, mention_detail, merge, merge_image, merge_layers, mesmerize, message, migrate, mimic_style, mind, mine, miss, mix, model, monitor, monitor_scene, mount, mount_horse, mourn, move, move_aside, move_back, move_camera, move_closer, move_down, move_forward, move_left, move_object, move_right, move_through, move_up, mow, multitask, murder, name, name_layer, nap, navigate, navigate_space, near, need, negotiate, nest, network, nominate, note, notice, nurture, observe, occur, offer, open, open_door, open_eyes, open_hand, operate, oppose, optimize, optimize_prompt, order, organize, organize_items, outline, outline_shape, outsource, overcome, overlook, own, pack, paddle, paint, paint_background, paint_skin, pair, pair_objects, parent, park, park_car, participate, partner, party, pass, pass_through, patrol, patrol_area, pave, pay, peek, peel, perform, perform_action, photograph, photograph_subject, pick, pick_up, pierce, pierce_surface, pin, pin_note, pioneer, pitch, place, place_hand, place_object, plan, plant, plant_feet, play, play_instrument, play_music, please, plot, point_at, polish, polish_surface, pose, pose_character, position, position_subject, post, pound, pour, pour_drink, pour_liquid, practice, praise, praise_subject, pray, preach, predict, prepare, prepare_scene, present, preserve, preserve_detail, press, press_button, press_key, pretend, prevent, prevent_overlap, price, print, print_image, process, process_image, produce, profile, program, progress, promise, promote, proof, propose, protect, protect_face, protest, provoke, prune, publish, pull, pull_back, pull_object, pull_rope, punch, purchase, pursue, push, push_button, push_object, qualify, question, queue, quit, quote, race, raft, rage, rain, raise, raise_arm, raise_hand, raise_head, rally, rank, rate, reach, reach_for, react, react_to, read, read_book, realize, rebuild, rebuild_scene, recall, receive, recognize, recommend, record, record_video, recover, recover_detail, recreate, recruit, recycle, reduce, refer, refine, refine_edges, refine_prompt, reflect, reflect_light, refresh, refresh_view, refuse, register, register_detail, regulate, rehearse, reign, relate, relax, relax_pose, release, release_arrow, rely, remain, remain_still, remember, remember_detail, remind, remove, remove_background, remove_noise, render, render_image, rent, reopen, reopen_file, repair, repair_object, replace, replace_object, report, reposition_object, request, require, rescue, rescue_subject, research, resist, resize_image, respect, respond, rest, rest_hand, restore, restore_image, restructure, result, retain, rethink, retire, return, reveal, reveal_face, review, revise, revisit, reward, ride, ride_vehicle, rip, risk, roam, roar, roast, rob, rock, roll, rotate, rotate_camera, rotate_object, row, rub, ruin, rule, run, run_away, run_forward, rush, sail, sample, sample_color, satisfy, save, save_image, scale_up, scan, scan_room, schedule, score, scout, scratch, sculpt, seal, seal_edge, search, search_area, search_room, season, seat, secure, secure_object, see, seed, seek, select, select_layer, select_object, sell, send, send_signal, sense, separate_layers, sequence, serve, set_scene, settle, settle_pose, sew, shade, shade_area, shade_skin, shadow, shake, shake_head, shame, shape, shape_light, share, share_prompt, sharpen_image, shave, shed, shift, shift_weight, shine, shine_light, ship, shock, shoot, shoot_arrow, shoot_photo, shop, shout, show, show_detail, show_object, showcase, shrink, shrink_object, shut, shut_door, signal, signal_help, sing, sip, sit, sit_down, size, sizzle, sketch, sketch_pose, ski, skip, slay, sleep, sleep_on, slide, slide_open, slip, slow, slow_down, smash, smell, smile, smile_at, smoke, smooth_skin, smuggle, snack, snap, snap_photo, sneak, sneak_past, snore, soak, soar, sob, soften_light, solve, solve_problem, soothe, sort, sort_items, sort_layers, source, source_light, span, spar, sparkle, speak, speak_to, speed, spell, spend, spill, spin, spin_around, spit, split, split_layer, split_screen, sponsor, sport, spot, spray, spread, spy, stab, stabilize_image, stack, staff, stage, stain, stain_surface, stake, stalk, stand, stand_up, stare, start, start_scene, starve, state, stay, stay_still, steam, steam_cloth, steer, steer_vehicle, step, step_aside, step_back, step_forward, stick, stimulate, stir, stitch, stock, stomp, stop, stop_motion, store, store_file, storm, stream, stream_video, strengthen, stress, stretch, stretch_arm, stretch_leg, strike, strip, strive, stroll, structure, structure_prompt, struggle, study, study_object, stuff, style, style_image, submit, submit_prompt, suffer, suggest, suit, supply, support, support_weight, surf, surround, survey, survey_scene, survive, sustain, swap, swap_color, swear, sweat, sweep, sweep_floor, swell, swim, swim_under, swing, switch, switch_view, tackle, tag, tag_image, tailor, tailor_outfit, take, take_photo, take_step, talk, talk_to, target_subject, taste, teach, tease, tease_hair, tempt, tend, terrify, test, test_prompt, testify, text, thank, threaten, thrill, thrive, throw, tilt_camera, tilt_head, tip, tire, top, touch, touch_face, tow, trace, trace_outline, track, track_subject, trade, trail, train, train_model, transfer, transfer_style, transform, transform_image, translate, translate_prompt, transport, travel, treat, trek, trend, trigger, trim, trim_edge, trust, trust_process, try, tune, tune_color, turn, turn_around, turn_head, tutor, tweet, type, type_text, unbox, unbox_item, undergo, understand, unite, unlock, unlock_door, unpack, unveil, unveil_subject, update, update_prompt, upgrade, upload, upload_image, urge, use, use_tool, validate, vary, vary_pose, view, view_image, violate, visit, visit_place, volunteer, vote, wait, wait_for, wake, walk, walk_away, walk_forward, walk_through, wander, want, warm, warm_light, warn, wash, wash_face, wash_hands, waste, watch, watch_scene, water, water_plant, wave, wave_hand, wax, wear, wear_mask, weave, weigh, welcome, weld, wheel, whip, whisper, widen, widen_frame, wield, wield_weapon, win, wind, wire, wish, witness, wonder, work, worry, wrap, wrap_cloth, wreck, wrestle, write, write_prompt, write_text, yell, zone, zoom_in, zoom_out diff --git a/web/vocab_multi_select.js b/web/vocab_multi_select.js new file mode 100644 index 0000000..e20452b --- /dev/null +++ b/web/vocab_multi_select.js @@ -0,0 +1,153 @@ +import { app } from "../../../scripts/app.js"; +import { api } from "../../../scripts/api.js"; + +const TARGET_NODE = "NukunVocabMultiStringList"; +const TARGET_DISPLAY_NAME = "Multi Vocab String List (Nukun)"; +const RANDOM_LABEL = "(random)"; +const SLOT_COUNT = 4; +const WORD_COUNT = 3; +const vocabCache = new Map(); +const configuredNodes = new WeakSet(); + +function widget(node, name) { + return node.widgets?.find((candidate) => candidate?.name === name); +} + +async function loadWords(vocabFile) { + if (!vocabFile) { + return []; + } + + if (vocabCache.has(vocabFile)) { + return vocabCache.get(vocabFile); + } + + const response = await api.fetchApi(`/nukun/vocab/words?file=${encodeURIComponent(vocabFile)}`, { + cache: "no-store", + }); + if (!response.ok) { + throw new Error(`Failed to load vocabulary: ${vocabFile}`); + } + + const payload = await response.json(); + const words = Array.isArray(payload.words) ? payload.words : []; + vocabCache.set(vocabFile, words); + return words; +} + +function configureWordWidget(wordWidget) { + if (!wordWidget) { + return; + } + + wordWidget.type = "combo"; + wordWidget.options = wordWidget.options || {}; + wordWidget.options.values = wordWidget.options.values || [RANDOM_LABEL]; + if (!wordWidget.value) { + wordWidget.value = RANDOM_LABEL; + } +} + +function updateWordWidget(wordWidget, words) { + if (!wordWidget) { + return; + } + + const values = [RANDOM_LABEL, ...words]; + wordWidget.type = "combo"; + wordWidget.options = wordWidget.options || {}; + wordWidget.options.values = values; + + if (!values.includes(wordWidget.value)) { + wordWidget.value = RANDOM_LABEL; + } +} + +async function updateSlotWords(node, slot) { + const vocabWidget = widget(node, `vocab_file_${slot}`); + const vocabFile = vocabWidget?.value; + let words = []; + + try { + words = await loadWords(vocabFile); + } catch (error) { + console.warn(`[Nukun] ${error.message}`); + } + + for (let wordIndex = 1; wordIndex <= WORD_COUNT; wordIndex += 1) { + updateWordWidget(widget(node, `word_${slot}_${wordIndex}`), words); + } + + app.graph?.setDirtyCanvas?.(true, true); +} + +function setupNode(node) { + node.serialize_widgets = true; + + for (let slot = 1; slot <= SLOT_COUNT; slot += 1) { + for (let wordIndex = 1; wordIndex <= WORD_COUNT; wordIndex += 1) { + configureWordWidget(widget(node, `word_${slot}_${wordIndex}`)); + } + + const vocabWidget = widget(node, `vocab_file_${slot}`); + if (vocabWidget) { + const previousCallback = vocabWidget.callback; + vocabWidget.callback = function (value, ...args) { + const result = previousCallback?.call(this, value, ...args); + updateSlotWords(node, slot); + return result; + }; + } + + updateSlotWords(node, slot); + } +} + +function isTargetNode(nodeType, nodeData) { + return ( + nodeData?.name === TARGET_NODE || + nodeData?.name === TARGET_DISPLAY_NAME || + nodeType?.comfyClass === TARGET_NODE || + nodeType?.title === TARGET_DISPLAY_NAME + ); +} + +function isTargetNodeInstance(node) { + return ( + node?.comfyClass === TARGET_NODE || + node?.comfyClass === TARGET_DISPLAY_NAME || + node?.type === TARGET_NODE || + node?.type === TARGET_DISPLAY_NAME || + node?.title === TARGET_DISPLAY_NAME + ); +} + +function setupTargetNode(node) { + if (!node || configuredNodes.has(node)) { + return; + } + + configuredNodes.add(node); + setupNode(node); +} + +app.registerExtension({ + name: "Nukun.VocabMultiSelect", + nodeCreated(node) { + if (isTargetNodeInstance(node)) { + setupTargetNode(node); + } + }, + async beforeRegisterNodeDef(nodeType, nodeData) { + if (!isTargetNode(nodeType, nodeData)) { + return; + } + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const result = onNodeCreated?.apply(this, arguments); + setupTargetNode(this); + return result; + }; + }, +});