Initial commit — ComfyUI Discord bot + web UI

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>
This commit is contained in:
Khoa (Revenovich) Tran Gia
2026-03-02 09:55:48 +07:00
commit 1ed3c9ec4b
82 changed files with 20693 additions and 0 deletions

268
commands/utility.py Normal file
View File

@@ -0,0 +1,268 @@
"""
commands/utility.py
===================
Quality-of-life utility commands for the Discord ComfyUI bot.
Commands provided:
- ping: Show bot latency (Discord WebSocket round-trip).
- status: Full overview of bot health, ComfyUI connectivity,
workflow state, and queue.
- queue-status: Quick view of pending job count and worker state.
- uptime: How long the bot has been running since it connected.
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from discord.ext import commands
logger = logging.getLogger(__name__)
def _format_uptime(start_time: datetime) -> str:
"""Return a human-readable uptime string from a UTC start datetime."""
delta = datetime.now(timezone.utc) - start_time
total_seconds = int(delta.total_seconds())
days, remainder = divmod(total_seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, seconds = divmod(remainder, 60)
if days:
return f"{days}d {hours}h {minutes}m {seconds}s"
if hours:
return f"{hours}h {minutes}m {seconds}s"
return f"{minutes}m {seconds}s"
def setup_utility_commands(bot):
"""
Register quality-of-life utility commands with the bot.
Parameters
----------
bot : commands.Bot
The Discord bot instance.
"""
@bot.command(name="ping", extras={"category": "Utility"})
async def ping_command(ctx: commands.Context) -> None:
"""
Show the bot's current Discord WebSocket latency.
Usage:
ttr!ping
"""
latency_ms = round(bot.latency * 1000)
await ctx.reply(f"Pong! Latency: **{latency_ms} ms**", mention_author=False)
@bot.command(name="status", extras={"category": "Utility"})
async def status_command(ctx: commands.Context) -> None:
"""
Show a full health overview of the bot and ComfyUI.
Displays:
- Bot latency and uptime
- ComfyUI server address and reachability
- Whether a workflow template is loaded
- Current workflow changes (prompt / negative_prompt / input_image)
- Job queue size and worker state
Usage:
ttr!status
"""
latency_ms = round(bot.latency * 1000)
# Uptime
if hasattr(bot, "start_time") and bot.start_time:
uptime_str = _format_uptime(bot.start_time)
else:
uptime_str = "N/A"
# ComfyUI info
comfy_ok = hasattr(bot, "comfy") and bot.comfy is not None
comfy_server = bot.comfy.server_address if comfy_ok else "not configured"
comfy_reachable = await bot.comfy.check_connection() if comfy_ok else False
workflow_loaded = comfy_ok and bot.comfy.get_workflow_template() is not None
# ComfyUI queue
comfy_pending = 0
comfy_running = 0
if comfy_ok:
q = await bot.comfy.get_comfy_queue()
if q:
comfy_pending = len(q.get("queue_pending", []))
comfy_running = len(q.get("queue_running", []))
# Workflow state summary
changes_parts: list[str] = []
if comfy_ok:
overrides = bot.comfy.state_manager.get_overrides()
if overrides.get("prompt"):
changes_parts.append("prompt")
if overrides.get("negative_prompt"):
changes_parts.append("negative_prompt")
if overrides.get("input_image"):
changes_parts.append(f"input_image: {overrides['input_image']}")
if overrides.get("seed") is not None:
changes_parts.append(f"seed={overrides['seed']}")
changes_summary = ", ".join(changes_parts) if changes_parts else "none"
conn_status = (
"reachable" if comfy_reachable
else ("unreachable" if comfy_ok else "not configured")
)
lines = [
"**Bot**",
f" Latency : {latency_ms} ms",
f" Uptime : {uptime_str}",
"",
f"**ComfyUI** — `{comfy_server}`",
f" Connection : {conn_status}",
f" Queue : {comfy_running} running, {comfy_pending} pending",
f" Workflow : {'loaded' if workflow_loaded else 'not loaded'}",
f" Changes set : {changes_summary}",
]
await ctx.reply("\n".join(lines), mention_author=False)
@bot.command(name="queue-status", aliases=["qs", "qstatus"], extras={"category": "Utility"})
async def queue_status_command(ctx: commands.Context) -> None:
"""
Show the current ComfyUI queue depth.
Usage:
ttr!queue-status
ttr!qs
"""
if not hasattr(bot, "comfy") or not bot.comfy:
await ctx.reply("ComfyUI client is not configured.", mention_author=False)
return
q = await bot.comfy.get_comfy_queue()
if q is None:
await ctx.reply("Could not reach ComfyUI server.", mention_author=False)
return
pending = len(q.get("queue_pending", []))
running = len(q.get("queue_running", []))
await ctx.reply(
f"ComfyUI queue: **{running}** running, **{pending}** pending.",
mention_author=False,
)
@bot.command(name="uptime", extras={"category": "Utility"})
async def uptime_command(ctx: commands.Context) -> None:
"""
Show how long the bot has been running since it last connected.
Usage:
ttr!uptime
"""
if not hasattr(bot, "start_time") or not bot.start_time:
await ctx.reply("Uptime information is not available.", mention_author=False)
return
uptime_str = _format_uptime(bot.start_time)
await ctx.reply(f"Uptime: **{uptime_str}**", mention_author=False)
@bot.command(name="comfy-stats", aliases=["cstats"], extras={"category": "Utility"})
async def comfy_stats_command(ctx: commands.Context) -> None:
"""
Show GPU and system stats from the ComfyUI server.
Displays OS, Python version, and per-device VRAM usage reported
by the ComfyUI ``/system_stats`` endpoint.
Usage:
ttr!comfy-stats
ttr!cstats
"""
if not hasattr(bot, "comfy") or not bot.comfy:
await ctx.reply("ComfyUI client is not configured.", mention_author=False)
return
stats = await bot.comfy.get_system_stats()
if stats is None:
await ctx.reply(
"Could not reach the ComfyUI server to fetch stats.", mention_author=False
)
return
system = stats.get("system", {})
devices = stats.get("devices", [])
lines = [
f"**ComfyUI System Stats** — `{bot.comfy.server_address}`",
f" OS : {system.get('os', 'N/A')}",
f" Python : {system.get('python_version', 'N/A')}",
]
if devices:
lines.append("")
lines.append("**Devices**")
for dev in devices:
name = dev.get("name", "unknown")
vram_total = dev.get("vram_total", 0)
vram_free = dev.get("vram_free", 0)
vram_used = vram_total - vram_free
def _mb(b: int) -> str:
return f"{b / 1024 / 1024:.0f} MB"
lines.append(
f" {name}{_mb(vram_used)} / {_mb(vram_total)} VRAM used"
)
else:
lines.append(" No device info available.")
await ctx.reply("\n".join(lines), mention_author=False)
@bot.command(name="comfy-queue", aliases=["cqueue", "cq"], extras={"category": "Utility"})
async def comfy_queue_command(ctx: commands.Context) -> None:
"""
Show the ComfyUI server's internal queue state.
Displays jobs currently running and pending on the ComfyUI server
itself (separate from the Discord bot's own job queue).
Usage:
ttr!comfy-queue
ttr!cq
"""
if not hasattr(bot, "comfy") or not bot.comfy:
await ctx.reply("ComfyUI client is not configured.", mention_author=False)
return
queue_data = await bot.comfy.get_comfy_queue()
if queue_data is None:
await ctx.reply(
"Could not reach the ComfyUI server to fetch queue info.", mention_author=False
)
return
running = queue_data.get("queue_running", [])
pending = queue_data.get("queue_pending", [])
lines = [
f"**ComfyUI Server Queue** — `{bot.comfy.server_address}`",
f" Running : {len(running)} job(s)",
f" Pending : {len(pending)} job(s)",
]
if running:
lines.append("")
lines.append("**Currently running**")
for entry in running[:5]: # cap at 5 to avoid huge messages
prompt_id = entry[1] if len(entry) > 1 else "unknown"
lines.append(f" `{prompt_id}`")
if pending:
lines.append("")
lines.append(f"**Pending** (showing up to 5 of {len(pending)})")
for entry in pending[:5]:
prompt_id = entry[1] if len(entry) > 1 else "unknown"
lines.append(f" `{prompt_id}`")
await ctx.reply("\n".join(lines), mention_author=False)