Improve random and mashup moodboard UX
Balance random selection across style families so photo-like boards do not dominate, and make mashups show the resolved source moodboards while accepting metadata_json, titles, search phrases, UUIDs, slugs, or URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -69,12 +69,37 @@ Use `style_only` if you want to feed the moodboard text into another prompt enha
|
||||
|
||||
### Krea Moodboard Random
|
||||
|
||||
Picks a deterministic random style by seed. Add a query to randomize within a style family, for example `query = cinematic noir` and `random_from_top_k = 25`.
|
||||
Picks a deterministic random style by seed. By default it uses `random_mode = balanced`, which samples a style family first so the largest family in the catalog, especially photo-like boards, does not dominate random results.
|
||||
|
||||
Random modes:
|
||||
|
||||
- `balanced`: recommended default; samples across style families first.
|
||||
- `any`: pure random from the whole matching pool.
|
||||
- `non_photo`: avoids boards classified as photo-family when possible.
|
||||
- `photo`: only photo-family boards when possible.
|
||||
|
||||
Add a query to randomize within a style family, for example `query = cinematic noir` and `random_from_top_k = 25`.
|
||||
|
||||
### Krea Moodboard Mashup
|
||||
|
||||
Blends two to four moodboards by concatenating transferable style guidance, deduping style axes and negative guidance.
|
||||
|
||||
What to put in each `board` box:
|
||||
|
||||
- Best option: paste or connect the `metadata_json` output from `Krea Moodboard Search`, `Krea Moodboard Random`, or `Krea Moodboard Style`.
|
||||
- Also works: a moodboard title, search phrase, UUID, slug, or Krea moodboard URL.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
Abyssal Gothic
|
||||
warm pastel product
|
||||
https://www.krea.ai/moodboard-feed/...
|
||||
{"uuid": "f8ba7b69-987b-5cc3-abd8-fc734a82223a"}
|
||||
```
|
||||
|
||||
The `preview` output lists which source moodboards were resolved. If the mashup looks wrong, check `preview` first and make the board boxes more specific.
|
||||
|
||||
### Krea Moodboard Style
|
||||
|
||||
Looks up one moodboard by exact title, slug, UUID, URL, or search phrase. This avoids a 2500-item dropdown while keeping every option findable.
|
||||
@@ -92,6 +117,8 @@ Looks up one moodboard by exact title, slug, UUID, URL, or search phrase. This a
|
||||
7. Connect `Krea Moodboard Apply.positive` to the Krea-2 Turbo subgraph `Text String (User Prompt)` input.
|
||||
8. If your workflow exposes a negative prompt or CFG path, connect `Krea Moodboard Apply.negative` there. If it does not, leave it unused.
|
||||
|
||||
For mashups, use two or more `Krea Moodboard Search` or `Krea Moodboard Random` nodes, then connect their `metadata_json` outputs into `Krea Moodboard Mashup.board_1`, `board_2`, etc. Connect the Mashup `positive` and `negative` outputs into `Krea Moodboard Apply`.
|
||||
|
||||
### Manual Krea 2 Graph
|
||||
|
||||
Use these nodes before Krea text encoding:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{ "name": "metadata_json", "type": "STRING", "links": [3] },
|
||||
{ "name": "preview", "type": "STRING", "links": null }
|
||||
],
|
||||
"widgets_values": ["dark teal gothic", 5, 1, 0, 0, "normal"]
|
||||
"widgets_values": ["dark teal gothic", 5, 1, 0, 0, "normal", "balanced"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{ "name": "title", "type": "STRING", "links": null },
|
||||
{ "name": "metadata_json", "type": "STRING", "links": [3] }
|
||||
],
|
||||
"widgets_values": [1234, "cinematic noir", 25, "normal"]
|
||||
"widgets_values": [1234, "cinematic noir", 25, "normal", "balanced"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 7,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KreaMoodboardSearch",
|
||||
"pos": [120, 120],
|
||||
"size": [360, 220],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{ "name": "positive", "type": "STRING", "links": null },
|
||||
{ "name": "negative", "type": "STRING", "links": null },
|
||||
{ "name": "title", "type": "STRING", "links": null },
|
||||
{ "name": "metadata_json", "type": "STRING", "links": [1] },
|
||||
{ "name": "preview", "type": "STRING", "links": null }
|
||||
],
|
||||
"widgets_values": ["dark teal gothic", 5, 1, 0, 0, "normal", "balanced"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "KreaMoodboardSearch",
|
||||
"pos": [120, 420],
|
||||
"size": [360, 220],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{ "name": "positive", "type": "STRING", "links": null },
|
||||
{ "name": "negative", "type": "STRING", "links": null },
|
||||
{ "name": "title", "type": "STRING", "links": null },
|
||||
{ "name": "metadata_json", "type": "STRING", "links": [2] },
|
||||
{ "name": "preview", "type": "STRING", "links": null }
|
||||
],
|
||||
"widgets_values": ["warm pastel product", 5, 1, 0, 0, "normal", "balanced"]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KreaMoodboardMashup",
|
||||
"pos": [560, 180],
|
||||
"size": [420, 300],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "board_1", "type": "STRING", "link": 1 },
|
||||
{ "name": "board_2", "type": "STRING", "link": 2 }
|
||||
],
|
||||
"outputs": [
|
||||
{ "name": "positive", "type": "STRING", "links": [3] },
|
||||
{ "name": "negative", "type": "STRING", "links": [4] },
|
||||
{ "name": "title", "type": "STRING", "links": null },
|
||||
{ "name": "metadata_json", "type": "STRING", "links": [5] },
|
||||
{ "name": "preview", "type": "STRING", "links": null }
|
||||
],
|
||||
"widgets_values": ["", "", 0.65, 0.35, "normal", "", "", 1.0, 1.0]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "KreaMoodboardApply",
|
||||
"pos": [1040, 180],
|
||||
"size": [420, 280],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{ "name": "prompt", "type": "STRING", "link": null },
|
||||
{ "name": "negative_prompt", "type": "STRING", "link": null },
|
||||
{ "name": "moodboard_positive", "type": "STRING", "link": 3 },
|
||||
{ "name": "moodboard_negative", "type": "STRING", "link": 4 },
|
||||
{ "name": "metadata_json", "type": "STRING", "link": 5 }
|
||||
],
|
||||
"outputs": [
|
||||
{ "name": "positive", "type": "STRING", "links": null },
|
||||
{ "name": "negative", "type": "STRING", "links": null },
|
||||
{ "name": "metadata_json", "type": "STRING", "links": null }
|
||||
],
|
||||
"widgets_values": [
|
||||
"a ceramic fox on a table, editorial product photo",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"{}",
|
||||
"normal",
|
||||
false,
|
||||
"newline"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[1, 1, 3, 3, 0, "STRING"],
|
||||
[2, 2, 3, 3, 1, "STRING"],
|
||||
[3, 3, 0, 4, 2, "STRING"],
|
||||
[4, 3, 1, 4, 3, "STRING"],
|
||||
[5, 3, 3, 4, 4, "STRING"]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"info": "Example mashup wiring: Search metadata_json outputs feed Mashup board inputs, then Mashup positive/negative feed Apply."
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
+66
-2
@@ -36,6 +36,16 @@ FIELD_WEIGHTS: tuple[tuple[str, int], ...] = (
|
||||
("negative_guidance", 3),
|
||||
)
|
||||
|
||||
STYLE_FAMILY_TERMS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("anime", ("anime", "manga", "kawaii", "chibi", "retroanime")),
|
||||
("illustration", ("illustration", "illustrated", "ink", "watercolor", "drawing", "sketch", "storybook")),
|
||||
("graphic", ("graphic", "poster", "typography", "halftone", "pop art", "comic", "vector")),
|
||||
("abstract", ("abstract", "abstraction", "geometric", "surreal", "spectral", "iridescent")),
|
||||
("3d", ("3d", "voxel", "isometric", "clay", "render", "cgi", "plastic")),
|
||||
("photo", ("photo", "photograph", "photographic", "photoreal", "documentary", "camera", "lens")),
|
||||
("cinematic", ("cinematic", "film", "35mm", "noir", "editorial")),
|
||||
)
|
||||
|
||||
|
||||
def load_catalog(path: str | Path = CATALOG_PATH) -> list[dict[str, Any]]:
|
||||
catalog_path = Path(path)
|
||||
@@ -113,6 +123,7 @@ def random_board(
|
||||
query: str = "",
|
||||
random_from_top_k: int = 0,
|
||||
min_score: int = 1,
|
||||
random_mode: str = "balanced",
|
||||
) -> dict[str, Any]:
|
||||
pool = catalog
|
||||
if query.strip():
|
||||
@@ -121,7 +132,21 @@ def random_board(
|
||||
pool = [match["board"] for match in matches]
|
||||
if not pool:
|
||||
raise ValueError("No Krea moodboards matched the query.")
|
||||
return random.Random(int(seed)).choice(pool)
|
||||
rng = random.Random(int(seed))
|
||||
if random_mode == "any":
|
||||
return rng.choice(pool)
|
||||
if random_mode == "photo":
|
||||
filtered = [board for board in pool if style_family(board) == "photo"]
|
||||
return rng.choice(filtered or pool)
|
||||
if random_mode == "non_photo":
|
||||
filtered = [board for board in pool if style_family(board) != "photo"]
|
||||
return rng.choice(filtered or pool)
|
||||
|
||||
grouped: dict[str, list[dict[str, Any]]] = {}
|
||||
for board in pool:
|
||||
grouped.setdefault(style_family(board), []).append(board)
|
||||
family = rng.choice(sorted(grouped))
|
||||
return rng.choice(grouped[family])
|
||||
|
||||
|
||||
def find_board(catalog: list[dict[str, Any]], value: str) -> dict[str, Any]:
|
||||
@@ -143,6 +168,27 @@ def find_board(catalog: list[dict[str, Any]], value: str) -> dict[str, Any]:
|
||||
raise ValueError(f"No Krea moodboard matched: {value}")
|
||||
|
||||
|
||||
def resolve_board_reference(catalog: list[dict[str, Any]], value: str) -> dict[str, Any]:
|
||||
"""Resolve title/search text, slug, UUID, URL, or metadata_json into a board."""
|
||||
raw = str(value or "").strip()
|
||||
if not raw:
|
||||
raise ValueError("Provide a moodboard title, search text, URL, UUID, slug, or metadata_json.")
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return find_board(catalog, raw)
|
||||
if not isinstance(data, dict):
|
||||
return find_board(catalog, raw)
|
||||
for key in ("uuid", "url", "slug", "title"):
|
||||
candidate = str(data.get(key) or "").strip()
|
||||
if candidate:
|
||||
try:
|
||||
return find_board(catalog, candidate)
|
||||
except ValueError:
|
||||
continue
|
||||
return find_board(catalog, raw)
|
||||
|
||||
|
||||
def style_from_board(board: dict[str, Any], *, strength: str = "normal") -> dict[str, str]:
|
||||
guidance = _guidance(board)
|
||||
style_axes = _string_list(guidance.get("style_axes"))
|
||||
@@ -246,15 +292,33 @@ def mashup_boards(
|
||||
style_axes.append(axis)
|
||||
sources.append({"title": title, "url": metadata.get("url", ""), "uuid": metadata.get("uuid", "")})
|
||||
|
||||
source_titles = [source["title"] for source in sources]
|
||||
mashup_title = "Mashup: " + " + ".join(source_titles[:4])
|
||||
metadata = {"source_count": len(sources), "sources": sources, "style_axes": style_axes}
|
||||
preview = "\n".join(
|
||||
f"{idx}. {source['title']} | {source['url']}" for idx, source in enumerate(sources, start=1)
|
||||
)
|
||||
return {
|
||||
"positive": "Blend these Krea moodboard styles: " + " | ".join(positives),
|
||||
"negative": " ".join(_dedupe(negatives)),
|
||||
"title": "Krea Moodboard Mashup",
|
||||
"title": mashup_title,
|
||||
"metadata_json": json.dumps(metadata, ensure_ascii=False, sort_keys=True),
|
||||
"preview": (
|
||||
"Mashup sources resolved. Paste metadata_json from Search/Random/Style nodes, "
|
||||
"or type a moodboard title, search phrase, UUID, slug, or Krea URL.\n"
|
||||
f"{preview}"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def style_family(board: dict[str, Any]) -> str:
|
||||
text = " ".join(_search_fields(board).values()).lower()
|
||||
for family, terms in STYLE_FAMILY_TERMS:
|
||||
if any(term in text for term in terms):
|
||||
return family
|
||||
return "other"
|
||||
|
||||
|
||||
def _score_board(board: dict[str, Any], expanded_terms: list[str]) -> tuple[int, list[str]]:
|
||||
score = 0
|
||||
matched: list[str] = []
|
||||
|
||||
@@ -10,6 +10,7 @@ from moodboard_catalog import (
|
||||
load_catalog,
|
||||
mashup_boards,
|
||||
random_board,
|
||||
resolve_board_reference,
|
||||
search_boards,
|
||||
style_from_board,
|
||||
)
|
||||
@@ -17,6 +18,7 @@ from moodboard_catalog import (
|
||||
|
||||
STRENGTHS = ["concise", "normal", "strong"]
|
||||
SEPARATORS = ["newline", "comma"]
|
||||
RANDOM_MODES = ["balanced", "any", "non_photo", "photo"]
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
@@ -110,10 +112,26 @@ class KreaMoodboardSearch:
|
||||
STRENGTHS,
|
||||
{"default": "normal", "tooltip": "Controls detail level in the positive style text."},
|
||||
),
|
||||
"random_mode": (
|
||||
RANDOM_MODES,
|
||||
{
|
||||
"default": "balanced",
|
||||
"tooltip": "balanced samples style families first; any samples the whole pool; non_photo avoids photo-family boards.",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
def search(self, query: str, top_k: int, min_score: int, random_from_top_k: int, seed: int, strength: str):
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
top_k: int,
|
||||
min_score: int,
|
||||
random_from_top_k: int,
|
||||
seed: int,
|
||||
strength: str,
|
||||
random_mode: str = "balanced",
|
||||
):
|
||||
catalog = _catalog()
|
||||
matches = search_boards(catalog, query, top_k=top_k, min_score=min_score)
|
||||
if not matches:
|
||||
@@ -125,6 +143,7 @@ class KreaMoodboardSearch:
|
||||
query=query,
|
||||
random_from_top_k=min(random_from_top_k, top_k),
|
||||
min_score=min_score,
|
||||
random_mode=random_mode,
|
||||
)
|
||||
else:
|
||||
board = matches[0]["board"]
|
||||
@@ -158,19 +177,39 @@ class KreaMoodboardRandom:
|
||||
{"default": 25, "min": 1, "max": 100, "step": 1, "tooltip": "Random pool size after search."},
|
||||
),
|
||||
"strength": (STRENGTHS, {"default": "normal"}),
|
||||
"random_mode": (
|
||||
RANDOM_MODES,
|
||||
{
|
||||
"default": "balanced",
|
||||
"tooltip": "balanced samples style families first so photo boards do not dominate random picks.",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
def random_style(self, seed: int, query: str, random_from_top_k: int, strength: str):
|
||||
board = random_board(_catalog(), seed=seed, query=query, random_from_top_k=random_from_top_k)
|
||||
def random_style(
|
||||
self,
|
||||
seed: int,
|
||||
query: str,
|
||||
random_from_top_k: int,
|
||||
strength: str,
|
||||
random_mode: str = "balanced",
|
||||
):
|
||||
board = random_board(
|
||||
_catalog(),
|
||||
seed=seed,
|
||||
query=query,
|
||||
random_from_top_k=random_from_top_k,
|
||||
random_mode=random_mode,
|
||||
)
|
||||
style = style_from_board(board, strength=strength)
|
||||
return style["positive"], style["negative"], style["title"], style["metadata_json"]
|
||||
|
||||
|
||||
class KreaMoodboardMashup:
|
||||
CATEGORY = "Krea/Moodboards"
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("positive", "negative", "title", "metadata_json")
|
||||
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING")
|
||||
RETURN_NAMES = ("positive", "negative", "title", "metadata_json", "preview")
|
||||
FUNCTION = "mashup"
|
||||
DESCRIPTION = "Blend two to four Krea moodboards into one prompt style."
|
||||
|
||||
@@ -178,15 +217,43 @@ class KreaMoodboardMashup:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"board_1": ("STRING", {"default": "", "multiline": False}),
|
||||
"board_2": ("STRING", {"default": "", "multiline": False}),
|
||||
"board_1": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Paste metadata_json from another Krea Moodboard node, or type a title, search phrase, UUID, slug, or URL.",
|
||||
},
|
||||
),
|
||||
"board_2": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Paste metadata_json from another Krea Moodboard node, or type a title, search phrase, UUID, slug, or URL.",
|
||||
},
|
||||
),
|
||||
"weight_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.05}),
|
||||
"weight_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.05}),
|
||||
"strength": (STRENGTHS, {"default": "normal"}),
|
||||
},
|
||||
"optional": {
|
||||
"board_3": ("STRING", {"default": "", "multiline": False}),
|
||||
"board_4": ("STRING", {"default": "", "multiline": False}),
|
||||
"board_3": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Optional third board: metadata_json, title, search phrase, UUID, slug, or URL.",
|
||||
},
|
||||
),
|
||||
"board_4": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Optional fourth board: metadata_json, title, search phrase, UUID, slug, or URL.",
|
||||
},
|
||||
),
|
||||
"weight_3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.05}),
|
||||
"weight_4": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.05}),
|
||||
},
|
||||
@@ -207,9 +274,9 @@ class KreaMoodboardMashup:
|
||||
catalog = _catalog()
|
||||
names = [board_1, board_2, board_3, board_4]
|
||||
weights = [weight_1, weight_2, weight_3, weight_4]
|
||||
boards = [find_board(catalog, name) for name in names if str(name or "").strip()]
|
||||
boards = [resolve_board_reference(catalog, name) for name in names if str(name or "").strip()]
|
||||
style = mashup_boards(boards, weights=weights[: len(boards)], strength=strength)
|
||||
return style["positive"], style["negative"], style["title"], style["metadata_json"]
|
||||
return style["positive"], style["negative"], style["title"], style["metadata_json"], style["preview"]
|
||||
|
||||
|
||||
class KreaMoodboardApply:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "comfyui-krea-moodboards"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
description = "Prompt-only Krea moodboard search and style nodes for ComfyUI."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from moodboard_catalog import CATALOG_PATH, load_catalog
|
||||
|
||||
|
||||
@@ -10,3 +13,13 @@ def test_bundled_catalog_contains_prompt_only_krea_moodboards() -> None:
|
||||
assert all(board.get("url", "").startswith("https://www.krea.ai/moodboard-feed/") for board in catalog)
|
||||
assert all("image_urls" not in board for board in catalog)
|
||||
assert all(board.get("qwen_guidance", {}).get("prompt_guidance") for board in catalog)
|
||||
|
||||
|
||||
def test_example_workflows_are_valid_json() -> None:
|
||||
example_paths = sorted(Path("examples").glob("*.json"))
|
||||
|
||||
assert example_paths
|
||||
for path in example_paths:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
assert payload.get("nodes"), path
|
||||
assert payload.get("version"), path
|
||||
|
||||
@@ -11,6 +11,7 @@ from moodboard_catalog import (
|
||||
load_catalog,
|
||||
mashup_boards,
|
||||
random_board,
|
||||
resolve_board_reference,
|
||||
search_boards,
|
||||
style_from_board,
|
||||
)
|
||||
@@ -102,6 +103,58 @@ def test_random_board_is_deterministic_and_can_use_top_matches(tmp_path: Path) -
|
||||
assert first["uuid"] == second["uuid"]
|
||||
|
||||
|
||||
def test_balanced_random_does_not_let_photo_styles_dominate() -> None:
|
||||
photo_boards = [
|
||||
{
|
||||
"url": f"https://www.krea.ai/moodboard-feed/photo-{index}",
|
||||
"slug": f"photo-{index}",
|
||||
"uuid": f"photo-{index}",
|
||||
"title": f"Photo Style {index}",
|
||||
"taste_profile": "cinematic photo lens documentary film look",
|
||||
"keywords": ["photo", "cinematic"],
|
||||
"qwen_guidance": {
|
||||
"prompt_guidance": "Use photographic lens lighting and cinematic film texture.",
|
||||
"negative_guidance": "",
|
||||
"style_axes": ["photographic"],
|
||||
"conditioning_notes": [],
|
||||
"source_summary": "Photo style.",
|
||||
},
|
||||
}
|
||||
for index in range(20)
|
||||
]
|
||||
illustration_board = {
|
||||
"url": "https://www.krea.ai/moodboard-feed/illustration",
|
||||
"slug": "illustration",
|
||||
"uuid": "illustration",
|
||||
"title": "Ink Illustration",
|
||||
"taste_profile": "flat ink drawing and illustrated poster language",
|
||||
"keywords": ["illustration", "ink"],
|
||||
"qwen_guidance": {
|
||||
"prompt_guidance": "Use ink illustration, flat shapes, and graphic poster texture.",
|
||||
"negative_guidance": "",
|
||||
"style_axes": ["illustration"],
|
||||
"conditioning_notes": [],
|
||||
"source_summary": "Illustration style.",
|
||||
},
|
||||
}
|
||||
catalog = photo_boards + [illustration_board]
|
||||
|
||||
picks = [random_board(catalog, seed=seed, random_mode="balanced") for seed in range(100)]
|
||||
|
||||
assert sum(board["uuid"] == "illustration" for board in picks) >= 25
|
||||
|
||||
|
||||
def test_resolve_board_reference_accepts_metadata_json_or_search_text(tmp_path: Path) -> None:
|
||||
catalog = load_catalog(write_catalog(tmp_path / "catalog.json"))
|
||||
metadata_ref = json.dumps({"uuid": "11111111-1111-5111-9111-111111111111"})
|
||||
|
||||
from_metadata = resolve_board_reference(catalog, metadata_ref)
|
||||
from_search = resolve_board_reference(catalog, "warm product")
|
||||
|
||||
assert from_metadata["title"] == "Abyssal Gothic"
|
||||
assert from_search["title"] == "Warm Product Pastel"
|
||||
|
||||
|
||||
def test_style_from_board_returns_positive_negative_and_metadata(tmp_path: Path) -> None:
|
||||
catalog = load_catalog(write_catalog(tmp_path / "catalog.json"))
|
||||
|
||||
@@ -140,6 +193,9 @@ def test_mashup_dedupes_negative_guidance_and_style_axes(tmp_path: Path) -> None
|
||||
|
||||
assert "Abyssal Gothic" in mashup["positive"]
|
||||
assert "Warm Product Pastel" in mashup["positive"]
|
||||
assert mashup["title"] == "Mashup: Abyssal Gothic + Warm Product Pastel"
|
||||
assert "Avoid flat bright daylight" in mashup["negative"]
|
||||
assert metadata["source_count"] == 2
|
||||
assert "deep teal" in metadata["style_axes"]
|
||||
assert "Abyssal Gothic" in mashup["preview"]
|
||||
assert "Warm Product Pastel" in mashup["preview"]
|
||||
|
||||
@@ -70,3 +70,55 @@ def test_apply_node_combines_prompt_and_style_metadata() -> None:
|
||||
assert "warm pastel lighting" in positive
|
||||
assert negative == "low quality, Avoid harsh contrast."
|
||||
assert json.loads(metadata_json)["title"] == "Warm Pastel"
|
||||
|
||||
|
||||
def test_mashup_node_accepts_metadata_json_and_outputs_preview(monkeypatch) -> None:
|
||||
boards = [
|
||||
{
|
||||
"url": "https://www.krea.ai/moodboard-feed/gothic",
|
||||
"slug": "gothic",
|
||||
"uuid": "gothic-uuid",
|
||||
"title": "Gothic Teal",
|
||||
"taste_profile": "Deep teal gothic style.",
|
||||
"keywords": ["gothic", "teal"],
|
||||
"qwen_guidance": {
|
||||
"prompt_guidance": "Use deep teal gothic lighting.",
|
||||
"negative_guidance": "Avoid flat daylight.",
|
||||
"style_axes": ["deep teal"],
|
||||
"conditioning_notes": [],
|
||||
"source_summary": "summary",
|
||||
},
|
||||
},
|
||||
{
|
||||
"url": "https://www.krea.ai/moodboard-feed/pastel",
|
||||
"slug": "pastel",
|
||||
"uuid": "pastel-uuid",
|
||||
"title": "Pastel Product",
|
||||
"taste_profile": "Warm product studio style.",
|
||||
"keywords": ["pastel", "product"],
|
||||
"qwen_guidance": {
|
||||
"prompt_guidance": "Use warm pastel studio lighting.",
|
||||
"negative_guidance": "Avoid harsh contrast.",
|
||||
"style_axes": ["warm pastel"],
|
||||
"conditioning_notes": [],
|
||||
"source_summary": "summary",
|
||||
},
|
||||
},
|
||||
]
|
||||
monkeypatch.setattr(nodes, "_catalog", lambda: boards)
|
||||
|
||||
positive, negative, title, metadata_json, preview = nodes.KreaMoodboardMashup().mashup(
|
||||
board_1=json.dumps({"uuid": "gothic-uuid"}),
|
||||
board_2="pastel product",
|
||||
weight_1=0.7,
|
||||
weight_2=0.3,
|
||||
strength="normal",
|
||||
)
|
||||
|
||||
assert title == "Mashup: Gothic Teal + Pastel Product"
|
||||
assert "Gothic Teal" in positive
|
||||
assert "Pastel Product" in positive
|
||||
assert "Avoid flat daylight" in negative
|
||||
assert json.loads(metadata_json)["source_count"] == 2
|
||||
assert "Gothic Teal" in preview
|
||||
assert "Paste metadata_json" in preview
|
||||
|
||||
@@ -8,7 +8,7 @@ def test_pyproject_has_comfy_registry_metadata() -> None:
|
||||
pyproject = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
||||
|
||||
assert pyproject["project"]["name"] == "comfyui-krea-moodboards"
|
||||
assert pyproject["project"]["version"] == "0.1.0"
|
||||
assert pyproject["project"]["version"] == "0.1.1"
|
||||
assert pyproject["project"]["dependencies"] == []
|
||||
assert pyproject["tool"]["comfy"]["PublisherId"] == "andrometa"
|
||||
assert pyproject["tool"]["comfy"]["DisplayName"] == "Krea Moodboards"
|
||||
|
||||
Reference in New Issue
Block a user