From acfc51135a8a68097e5d59c7a66e1e62387d5db2 Mon Sep 17 00:00:00 2001 From: OnekoSL Date: Wed, 10 Jun 2026 20:24:35 +0200 Subject: [PATCH] Add Z-Image prompt refining and Qwen conditioning support --- CHANGELOG.md | 5 + README.md | 19 +- Z-Image Promptgestaltung.pdf | Bin 0 -> 158262 bytes __init__.py | 4 + nodes/ollama_prompt_refiner.py | 2148 ++++++++++++++++++++++ nodes/random_vocab_string_list.py | 189 ++ nodes/t5_equal_length_balancer.py | 52 +- nodes/t5_sculpt_equal_length_balancer.py | 76 +- resources/adjectives.csv | 2 +- resources/animals_mythical_creatures.csv | 2 +- resources/camera_composition.csv | 1 + resources/ing_words.csv | 2 +- resources/objects.csv | 2 +- resources/verbs.csv | 2 +- web/vocab_multi_select.js | 153 ++ 15 files changed, 2596 insertions(+), 61 deletions(-) create mode 100644 Z-Image Promptgestaltung.pdf create mode 100644 nodes/ollama_prompt_refiner.py create mode 100644 resources/camera_composition.csv create mode 100644 web/vocab_multi_select.js 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 0000000000000000000000000000000000000000..faede4cbb732bf607baa923c387fb39912b5f97f GIT binary patch literal 158262 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�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
    a#v(BhClq!E{U#C=ial*%nd39yb6RTxfFH5 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 iQJ$ 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; + }; + }, +});