refactor: rename Style Pick \u2192 Pick Style, Style Blend \u2192 Blend Style (verb-first node names)
This commit is contained in:
+4
-4
@@ -9,9 +9,9 @@ from comfy_api.latest import ComfyExtension, io
|
||||
|
||||
from .src.immac_tools.api import register_routes
|
||||
from .src.immac_tools.style_mix_node import PickMixNode, StyleMixNode
|
||||
from .src.immac_tools.style_pick_node import StylePickNode
|
||||
from .src.immac_tools.style_pick_node import PickStyleNode
|
||||
from .src.immac_tools.style_weight_node import StyleWeightNode
|
||||
from .src.immac_tools.style_blend_node import StyleBlendNode
|
||||
from .src.immac_tools.style_blend_node import BlendStyleNode
|
||||
from .src.immac_tools.save_mix_node import SaveMixNode
|
||||
from .src.immac_tools.save_style_node import SaveStyleNode
|
||||
|
||||
@@ -20,9 +20,9 @@ class ImmacStyleMixerExtension(ComfyExtension):
|
||||
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||
return [
|
||||
PickMixNode,
|
||||
StylePickNode,
|
||||
PickStyleNode,
|
||||
StyleWeightNode,
|
||||
StyleBlendNode,
|
||||
BlendStyleNode,
|
||||
SaveMixNode,
|
||||
SaveStyleNode,
|
||||
]
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -47,11 +47,19 @@ _MODE = [
|
||||
]
|
||||
|
||||
|
||||
def _update_mix(mix: dict, mix_styles: list, example_image, name: str) -> None:
|
||||
"""Patch styles and optionally the image on an existing mix dict in place."""
|
||||
def _update_mix(
|
||||
mix: dict,
|
||||
mix_styles: list,
|
||||
example_image,
|
||||
name: str,
|
||||
favorite: bool | None = None,
|
||||
) -> None:
|
||||
"""Patch styles and optionally the image/name/favorite on an existing mix dict in place."""
|
||||
mix["styles"] = mix_styles
|
||||
if name:
|
||||
mix["name"] = name
|
||||
if favorite is not None:
|
||||
mix["favorite"] = favorite
|
||||
if example_image is not None:
|
||||
mix["image_filename"] = _save_mix_image(
|
||||
example_image,
|
||||
@@ -61,27 +69,64 @@ def _update_mix(mix: dict, mix_styles: list, example_image, name: str) -> None:
|
||||
mix["image_updated_at"] = int(time.time())
|
||||
|
||||
|
||||
def _derive_name(mix_styles: list, data: dict) -> str:
|
||||
"""Build a 'Quick Mix (A, B, C)' label from the style names in blend order.
|
||||
|
||||
Unresolved style_ids fall back to the first 8 hex chars of the id.
|
||||
"""
|
||||
style_index = {s["id"]: s.get("name", "") for s in data.get("styles", [])}
|
||||
parts = []
|
||||
for entry in mix_styles:
|
||||
sid = entry.get("style_id", "")
|
||||
parts.append(style_index.get(sid) or sid[:8])
|
||||
joined = ", ".join(parts) if parts else "empty"
|
||||
return f"Quick Mix ({joined})"
|
||||
|
||||
|
||||
class SaveMixNode(io.ComfyNode):
|
||||
"""Creates or updates a saved mix from a blend_json (output of Style Blend).
|
||||
|
||||
mode controls how the target record is resolved and what happens when it
|
||||
does or does not exist:
|
||||
|
||||
Create — inserts a new mix. Requires a non-empty name. The id input
|
||||
must not be connected (ids are assigned automatically).
|
||||
Create — inserts a new mix. The id input must not be connected (ids are
|
||||
assigned automatically). If name is empty an auto-name of the
|
||||
form 'Quick Mix (A, B, C)' is derived from the blend contents.
|
||||
Fails if a mix with the same name already exists.
|
||||
|
||||
Update — modifies an existing mix (by id or by name); fails if not found.
|
||||
Connect either an id or enter a name — not both.
|
||||
|
||||
Save — updates if found (by id or by name), creates if not found by
|
||||
name. Fails if id is given but no match exists. Requires either
|
||||
an id or a name, but not both.
|
||||
name. If name is empty on create an auto-name is derived.
|
||||
Fails if id is given but no match exists.
|
||||
|
||||
The blend_json input must be wired from the blend_json output of a Style
|
||||
Blend node. Each entry in that JSON array encodes a {style_id, weight}
|
||||
pair that will be stored in the mix exactly as produced by Style Blend
|
||||
(including any deduplication / normalisation already applied there).
|
||||
|
||||
Behaviour matrix (id = connected, name = non-empty, * = auto-derived):
|
||||
|
||||
mode id name found? result
|
||||
Create – – exists ✅ replace existing Quick Mix (auto-derived name upsert)
|
||||
Create – – – ✅ insert new record (name auto-derived)
|
||||
Create – ✓ exists error: name already taken; use Update or Save
|
||||
Create – ✓ – ✅ insert new record
|
||||
Create ✓ – – error: id must not be connected in Create mode
|
||||
Create ✓ ✓ – error: id must not be connected in Create mode
|
||||
Update – – – error: id or name required
|
||||
Update – ✓ exists ✅ update by name
|
||||
Update – ✓ – error: no mix named '{name}'
|
||||
Update ✓ – exists ✅ update by id
|
||||
Update ✓ – – error: no mix with id '{id}'
|
||||
Update ✓ ✓ – error: provide id OR name, not both
|
||||
Save – – – ✅ insert new record (name auto-derived)
|
||||
Save – ✓ exists ✅ update by name
|
||||
Save – ✓ – ✅ insert new record
|
||||
Save ✓ – exists ✅ update by id
|
||||
Save ✓ – – error: no mix with id '{id}'; provide a name to create
|
||||
Save ✓ ✓ – error: provide id OR name, not both
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -105,10 +150,12 @@ class SaveMixNode(io.ComfyNode):
|
||||
io.String.Input("name", default="", multiline=False, optional=True),
|
||||
io.Combo.Input("mode", options=_MODE, default="Save"),
|
||||
io.String.Input("id", optional=True, default="", multiline=False),
|
||||
io.Boolean.Input("favorite", optional=True, default=False),
|
||||
io.Image.Input("example_image", optional=True),
|
||||
],
|
||||
outputs=[
|
||||
io.String.Output(display_name="mix_id"),
|
||||
io.String.Output(display_name="mix_name"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -126,6 +173,7 @@ class SaveMixNode(io.ComfyNode):
|
||||
name: str = "",
|
||||
mode: str = "Save",
|
||||
id: str = "",
|
||||
favorite: bool = False,
|
||||
example_image=None,
|
||||
) -> io.NodeOutput:
|
||||
name = (name or "").strip()
|
||||
@@ -138,18 +186,14 @@ class SaveMixNode(io.ComfyNode):
|
||||
"— ids are assigned automatically."
|
||||
)
|
||||
|
||||
# ── Guard: Create mode requires a name ───────────────────────────────
|
||||
if mode == "Create" and not name:
|
||||
raise ValueError("[SaveMixNode] A name is required to create a new mix.")
|
||||
|
||||
# ── Guard: Update / Save must not have both id and name ──────────────
|
||||
if mode in ("Update", "Save") and mix_id_input and name:
|
||||
raise ValueError(
|
||||
"[SaveMixNode] Connect either an id or enter a name — not both."
|
||||
)
|
||||
|
||||
# ── Guard: Update / Save require at least one identifier ─────────────
|
||||
if mode in ("Update", "Save") and not mix_id_input and not name:
|
||||
# ── Guard: Update requires at least one identifier ───────────────────
|
||||
if mode == "Update" and not mix_id_input and not name:
|
||||
raise ValueError(
|
||||
"[SaveMixNode] Connect an id or enter a name to identify the mix."
|
||||
)
|
||||
@@ -179,23 +223,39 @@ class SaveMixNode(io.ComfyNode):
|
||||
if mix_id_input:
|
||||
existing = next((m for m in mixes if m.get("id") == mix_id_input), None)
|
||||
else:
|
||||
existing = next((m for m in mixes if m.get("name") == name), None)
|
||||
existing = next((m for m in mixes if m.get("name") == name), None) if name else None
|
||||
|
||||
# ── Mode: Create ─────────────────────────────────────────────────────
|
||||
if mode == "Create":
|
||||
# Auto-derive a name if none was given
|
||||
name_was_derived = not name
|
||||
if name_was_derived:
|
||||
name = _derive_name(mix_styles, data)
|
||||
if existing is None and name:
|
||||
existing = next((m for m in mixes if m.get("name") == name), None)
|
||||
if existing is not None:
|
||||
if name_was_derived:
|
||||
# Quick Mix names are temporary — replace the existing record
|
||||
_update_mix(existing, mix_styles, example_image, name, favorite)
|
||||
save_data(data)
|
||||
return io.NodeOutput(existing["id"], existing["name"])
|
||||
raise RuntimeError(
|
||||
f"[SaveMixNode] A mix named '{name}' already exists "
|
||||
f"(id={existing['id']}). Switch to Update or Save mode to modify it."
|
||||
)
|
||||
new_mix_id = str(uuid.uuid4())
|
||||
new_mix: dict = {"id": new_mix_id, "name": name, "styles": mix_styles}
|
||||
new_mix: dict = {
|
||||
"id": new_mix_id,
|
||||
"name": name,
|
||||
"favorite": favorite if favorite is not None else False,
|
||||
"styles": mix_styles,
|
||||
}
|
||||
if example_image is not None:
|
||||
new_mix["image_filename"] = _save_mix_image(example_image, name)
|
||||
new_mix["image_updated_at"] = int(time.time())
|
||||
mixes.append(new_mix)
|
||||
save_data(data)
|
||||
return io.NodeOutput(new_mix_id)
|
||||
return io.NodeOutput(new_mix_id, name)
|
||||
|
||||
# ── Mode: Update ─────────────────────────────────────────────────────
|
||||
if mode == "Update":
|
||||
@@ -208,9 +268,9 @@ class SaveMixNode(io.ComfyNode):
|
||||
raise RuntimeError(
|
||||
f"[SaveMixNode] No mix named '{name}' was found."
|
||||
)
|
||||
_update_mix(existing, mix_styles, example_image, name)
|
||||
_update_mix(existing, mix_styles, example_image, name, favorite)
|
||||
save_data(data)
|
||||
return io.NodeOutput(existing["id"])
|
||||
return io.NodeOutput(existing["id"], existing["name"])
|
||||
|
||||
# ── Mode: Save ───────────────────────────────────────────────────────
|
||||
if mix_id_input and existing is None:
|
||||
@@ -220,16 +280,23 @@ class SaveMixNode(io.ComfyNode):
|
||||
)
|
||||
|
||||
if existing is not None:
|
||||
_update_mix(existing, mix_styles, example_image, name)
|
||||
_update_mix(existing, mix_styles, example_image, name, favorite)
|
||||
save_data(data)
|
||||
return io.NodeOutput(existing["id"])
|
||||
return io.NodeOutput(existing["id"], existing["name"])
|
||||
else:
|
||||
# Save + name + not found → create
|
||||
# Save + (name or auto-derive) + not found → create
|
||||
if not name:
|
||||
name = _derive_name(mix_styles, data)
|
||||
new_mix_id = str(uuid.uuid4())
|
||||
new_mix = {"id": new_mix_id, "name": name, "styles": mix_styles}
|
||||
new_mix = {
|
||||
"id": new_mix_id,
|
||||
"name": name,
|
||||
"favorite": favorite if favorite is not None else False,
|
||||
"styles": mix_styles,
|
||||
}
|
||||
if example_image is not None:
|
||||
new_mix["image_filename"] = _save_mix_image(example_image, name)
|
||||
new_mix["image_updated_at"] = int(time.time())
|
||||
mixes.append(new_mix)
|
||||
save_data(data)
|
||||
return io.NodeOutput(new_mix_id)
|
||||
return io.NodeOutput(new_mix_id, name)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""StyleBlendNode — aggregates weighted style_entry slots into a blend JSON + prompt."""
|
||||
"""BlendStyleNode — aggregates weighted style_entry slots into a blend JSON + prompt."""
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -22,7 +22,7 @@ _WEIGHT_MODE = [
|
||||
]
|
||||
|
||||
|
||||
class StyleBlendNode(io.ComfyNode):
|
||||
class BlendStyleNode(io.ComfyNode):
|
||||
"""Aggregates style_entry inputs (from Weight Style nodes) into a blend.
|
||||
|
||||
Each slot accepts a style_entry JSON string produced by the Weight Style node.
|
||||
@@ -50,8 +50,8 @@ class StyleBlendNode(io.ComfyNode):
|
||||
max=16,
|
||||
)
|
||||
return io.Schema(
|
||||
node_id="StyleBlendImmacStyleMixer",
|
||||
display_name="Style Blend",
|
||||
node_id="BlendStyleImmacStyleMixer",
|
||||
display_name="Blend Style",
|
||||
category="Immac/Style Mixer",
|
||||
description=(
|
||||
"Aggregates weighted style_entry inputs (from Weight Style nodes) "
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""StylePickNode — picks a saved style and outputs its name, id, and value."""
|
||||
"""PickStyleNode — picks a saved style and outputs its name, id, and value."""
|
||||
|
||||
import os
|
||||
|
||||
@@ -13,14 +13,14 @@ def _style_names() -> list[str]:
|
||||
return names if names else ["(no styles saved)"]
|
||||
|
||||
|
||||
class StylePickNode(io.ComfyNode):
|
||||
class PickStyleNode(io.ComfyNode):
|
||||
"""Picks a saved style and outputs its name, id, and prompt value."""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="StylePickImmacStyleMixer",
|
||||
display_name="Style Pick",
|
||||
node_id="PickStyleImmacStyleMixer",
|
||||
display_name="Pick Style",
|
||||
category="Immac/Style Mixer",
|
||||
description="Picks a saved style and outputs its name, id, and prompt value.",
|
||||
inputs=[
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function StyleCard({ style, onUpdate, onDelete, inCurrentMix, onA
|
||||
if (document.elementFromPoint(e.clientX, e.clientY) !== canvasEl) return
|
||||
const pos: [number, number] = lgCanvas.convertEventToCanvasOffset(e)
|
||||
const LG = (window as any).LiteGraph
|
||||
const node = LG?.createNode('StylePickImmacStyleMixer')
|
||||
const node = LG?.createNode('PickStyleImmacStyleMixer')
|
||||
if (!node) return
|
||||
node.pos = pos
|
||||
;(window as any).app?.graph?.add(node)
|
||||
|
||||
+5
-5
@@ -168,11 +168,11 @@ async function init(): Promise<void> {
|
||||
nodeCreated(node: any) {
|
||||
if (
|
||||
node.comfyClass !== 'PickMixImmacStyleMixer' &&
|
||||
node.comfyClass !== 'StylePickImmacStyleMixer'
|
||||
node.comfyClass !== 'PickStyleImmacStyleMixer'
|
||||
) return
|
||||
|
||||
// ── shared DOM preview widget ────────────────────────────────────────
|
||||
const isPickNode = node.comfyClass === 'StylePickImmacStyleMixer'
|
||||
// ── shared DOM preview widget ────────────────────────────────────────────────────────────────────
|
||||
const isPickNode = node.comfyClass === 'PickStyleImmacStyleMixer'
|
||||
const widgetName = isPickNode ? 'style' : 'mix'
|
||||
|
||||
const comboWidget = node.widgets?.find((w: any) => w.name === widgetName)
|
||||
@@ -289,7 +289,7 @@ async function init(): Promise<void> {
|
||||
|
||||
node._immacPreviewScheduled = false
|
||||
|
||||
const isPickNode = node.comfyClass === 'StylePickImmacStyleMixer'
|
||||
const isPickNode = node.comfyClass === 'PickStyleImmacStyleMixer'
|
||||
const widgetName = isPickNode ? 'style' : 'mix'
|
||||
const comboWidget = node.widgets?.find((w: any) => w.name === widgetName)
|
||||
const currentVal = String(comboWidget?.value ?? '')
|
||||
@@ -321,7 +321,7 @@ async function init(): Promise<void> {
|
||||
// We listen on `execution_success` (fires once per completed queue item)
|
||||
// via the directly-imported `api` singleton. Any run could have updated an
|
||||
// image_filename via SaveMix / CreateStyle / ModifyStyle, so we refresh all
|
||||
// PickMix and StylePick node previews. The image_updated_at cache-buster
|
||||
// PickMix and Pick Style node previews. The image_updated_at cache-buster
|
||||
// ensures the browser fetches the new file only when it actually changed.
|
||||
function refreshAllPreviews() {
|
||||
const nodes: any[] = (app as any).graph?._nodes ?? []
|
||||
|
||||
Reference in New Issue
Block a user