""" commands/workflow_changes.py ============================ Workflow override management commands for the Discord ComfyUI bot. Works with any NodeInput.key discovered by WorkflowInspector — not just the four original hard-coded keys. Backward-compat aliases are preserved: ``type:prompt``, ``type:negative_prompt``, ``type:input_image``, ``type:seed``. """ from __future__ import annotations import logging from discord.ext import commands from config import ARG_TYPE_KEY from discord_utils import require_comfy_client logger = logging.getLogger(__name__) def setup_workflow_changes_commands(bot): """Register workflow changes commands with the bot.""" @bot.command( name="get-current-workflow-changes", aliases=["getworkflowchanges", "gcwc"], extras={"category": "Workflow"}, ) @require_comfy_client async def get_current_workflow_changes_command( ctx: commands.Context, *, args: str = "" ) -> None: """ Show current workflow override values. Usage:: ttr!get-current-workflow-changes type:all ttr!get-current-workflow-changes type:prompt ttr!get-current-workflow-changes type: """ try: overrides = bot.comfy.state_manager.get_overrides() if ARG_TYPE_KEY not in args: await ctx.reply( f"Use `{ARG_TYPE_KEY}all` to see all overrides, or " f"`{ARG_TYPE_KEY}` for a specific key.", mention_author=False, ) return param = args.split(ARG_TYPE_KEY, 1)[1].strip().lower() if param == "all": if not overrides: await ctx.reply("No overrides set.", mention_author=False) return lines = [f"**{k}**: `{v}`" for k, v in sorted(overrides.items())] await ctx.reply( "Current overrides:\n" + "\n".join(lines), mention_author=False, ) else: # Support multi-word value with the key as prefix key = param.split()[0] if " " in param else param val = overrides.get(key) if val is None: await ctx.reply( f"Override `{key}` is not set.", mention_author=False, ) else: await ctx.reply( f"**{key}**: `{val}`", mention_author=False, ) except Exception as exc: logger.exception("Failed to get workflow overrides") await ctx.reply(f"An error occurred: {type(exc).__name__}: {exc}", mention_author=False) @bot.command( name="set-current-workflow-changes", aliases=["setworkflowchanges", "scwc"], extras={"category": "Workflow"}, ) @require_comfy_client async def set_current_workflow_changes_command( ctx: commands.Context, *, args: str = "" ) -> None: """ Set a workflow override value. Supports any NodeInput.key discovered by WorkflowInspector as well as the legacy fixed keys. Usage:: ttr!set-current-workflow-changes type: Examples:: ttr!scwc type:prompt A beautiful landscape ttr!scwc type:negative_prompt blurry ttr!scwc type:input_image my_image.png ttr!scwc type:steps 30 ttr!scwc type:cfg 7.5 ttr!scwc type:seed 42 """ try: if not args or ARG_TYPE_KEY not in args: await ctx.reply( f"Usage: `ttr!set-current-workflow-changes {ARG_TYPE_KEY} `", mention_author=False, ) return rest = args.split(ARG_TYPE_KEY, 1)[1] # Key is the first word; value is everything after the first space parts = rest.split(None, 1) if len(parts) < 2: await ctx.reply( "Please provide both a key and a value. " f"Example: `ttr!scwc {ARG_TYPE_KEY}prompt A cat`", mention_author=False, ) return key = parts[0].strip().lower() raw_value: str = parts[1].strip() if not key: await ctx.reply("Key cannot be empty.", mention_author=False) return # Type-coerce well-known numeric keys _int_keys = {"steps", "width", "height"} _float_keys = {"cfg", "denoise"} _seed_keys = {"seed", "noise_seed"} value: object = raw_value try: if key in _int_keys: value = int(raw_value) elif key in _float_keys: value = float(raw_value) elif key in _seed_keys: value = int(raw_value) except ValueError: await ctx.reply( f"Invalid value for `{key}`: expected a number, got `{raw_value}`.", mention_author=False, ) return bot.comfy.state_manager.set_override(key, value) await ctx.reply( f"Override **{key}** set to `{value}`.", mention_author=False, ) except Exception as exc: logger.exception("Failed to set workflow override") await ctx.reply(f"An error occurred: {type(exc).__name__}: {exc}", mention_author=False) @bot.command( name="clear-workflow-change", aliases=["clearworkflowchange", "cwc"], extras={"category": "Workflow"}, ) @require_comfy_client async def clear_workflow_change_command( ctx: commands.Context, *, args: str = "" ) -> None: """ Remove a single override key. Usage:: ttr!clear-workflow-change type: """ try: if ARG_TYPE_KEY not in args: await ctx.reply( f"Usage: `ttr!clear-workflow-change {ARG_TYPE_KEY}`", mention_author=False, ) return key = args.split(ARG_TYPE_KEY, 1)[1].strip().lower() bot.comfy.state_manager.delete_override(key) await ctx.reply(f"Override **{key}** cleared.", mention_author=False) except Exception as exc: logger.exception("Failed to clear override") await ctx.reply(f"An error occurred: {type(exc).__name__}: {exc}", mention_author=False) @bot.command( name="set-seed", aliases=["setseed"], extras={"category": "Workflow"}, ) @require_comfy_client async def set_seed_command(ctx: commands.Context, *, args: str = "") -> None: """ Pin a specific seed for deterministic generation. Usage:: ttr!set-seed 42 """ seed_str = args.strip() if not seed_str: await ctx.reply("Usage: `ttr!set-seed `", mention_author=False) return if not seed_str.isdigit(): await ctx.reply("Seed must be a non-negative integer.", mention_author=False) return seed_val = int(seed_str) max_seed = 2 ** 32 - 1 if seed_val > max_seed: await ctx.reply(f"Seed must be between 0 and {max_seed}.", mention_author=False) return try: bot.comfy.state_manager.set_seed(seed_val) await ctx.reply(f"Seed pinned to `{seed_val}`.", mention_author=False) except Exception as exc: logger.exception("Failed to set seed") await ctx.reply(f"An error occurred: {type(exc).__name__}: {exc}", mention_author=False) @bot.command( name="clear-seed", aliases=["clearseed"], extras={"category": "Workflow"}, ) @require_comfy_client async def clear_seed_command(ctx: commands.Context) -> None: """ Clear the pinned seed and return to random generation. Usage:: ttr!clear-seed """ try: bot.comfy.state_manager.clear_seed() await ctx.reply("Seed cleared; generation will now use random seeds.", mention_author=False) except Exception as exc: logger.exception("Failed to clear seed") await ctx.reply(f"An error occurred: {type(exc).__name__}: {exc}", mention_author=False)