Full source for the-third-rev: Discord bot (discord.py), FastAPI web UI (React/TS/Vite/Tailwind), ComfyUI integration, generation history DB, preset manager, workflow inspector, and all supporting modules. Excluded from tracking: .env, invite_tokens.json, *.db (SQLite), current-workflow-changes.json, user_settings/, presets/, logs/, web-static/ (build output), frontend/node_modules/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
253 lines
8.5 KiB
Python
253 lines
8.5 KiB
Python
"""
|
|
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:<any_override_key>
|
|
"""
|
|
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}<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:<key> <value>
|
|
|
|
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}<key> <value>`",
|
|
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:<key>
|
|
"""
|
|
try:
|
|
if ARG_TYPE_KEY not in args:
|
|
await ctx.reply(
|
|
f"Usage: `ttr!clear-workflow-change {ARG_TYPE_KEY}<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 <number>`", 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)
|