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:
780
DEVELOPMENT.md
Normal file
780
DEVELOPMENT.md
Normal file
@@ -0,0 +1,780 @@
|
||||
# Development Guide
|
||||
|
||||
This guide explains how to add new commands, features, and modules to the Discord ComfyUI bot.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Adding a New Command](#adding-a-new-command)
|
||||
- [Adding a New Feature Module](#adding-a-new-feature-module)
|
||||
- [Adding Configuration Options](#adding-configuration-options)
|
||||
- [Working with Workflows](#working-with-workflows)
|
||||
- [Best Practices](#best-practices)
|
||||
- [Common Patterns](#common-patterns)
|
||||
- [Testing Your Changes](#testing-your-changes)
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Command
|
||||
|
||||
Commands are organized in the `commands/` directory by functionality. Here's how to add a new command:
|
||||
|
||||
### Step 1: Choose the Right Module
|
||||
|
||||
Determine which existing command module your command belongs to:
|
||||
|
||||
- **generation.py** - Image/video generation commands
|
||||
- **workflow.py** - Workflow template management
|
||||
- **upload.py** - File upload commands
|
||||
- **history.py** - History viewing and retrieval
|
||||
- **workflow_changes.py** - Runtime workflow parameter management
|
||||
|
||||
If none fit, create a new module (see [Adding a New Feature Module](#adding-a-new-feature-module)).
|
||||
|
||||
### Step 2: Add Your Command Function
|
||||
|
||||
Edit the appropriate module in `commands/` and add your command to the `setup_*_commands()` function:
|
||||
|
||||
```python
|
||||
# commands/generation.py
|
||||
|
||||
def setup_generation_commands(bot, config):
|
||||
# ... existing commands ...
|
||||
|
||||
@bot.command(name="my-new-command", aliases=["mnc", "my-cmd"])
|
||||
@require_comfy_client # Use this decorator if you need bot.comfy
|
||||
async def my_new_command(ctx: commands.Context, *, args: str = "") -> None:
|
||||
"""
|
||||
Brief description of what your command does.
|
||||
|
||||
Usage:
|
||||
ttr!my-new-command [arguments]
|
||||
|
||||
Longer description with examples and details.
|
||||
"""
|
||||
# Parse arguments if needed
|
||||
if not args:
|
||||
await ctx.reply("Please provide arguments!", mention_author=False)
|
||||
return
|
||||
|
||||
try:
|
||||
# Your command logic here
|
||||
result = await bot.comfy.some_method(args)
|
||||
|
||||
# Send response
|
||||
await ctx.reply(f"Success! Result: {result}", mention_author=False)
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to execute my-new-command")
|
||||
await ctx.reply(
|
||||
f"An error occurred: {type(exc).__name__}: {exc}",
|
||||
mention_author=False,
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Import Required Dependencies
|
||||
|
||||
At the top of your command module, import what you need:
|
||||
|
||||
```python
|
||||
import logging
|
||||
from discord.ext import commands
|
||||
from discord_utils import require_comfy_client, parse_labeled_args
|
||||
from config import ARG_PROMPT_KEY, ARG_TYPE_KEY
|
||||
from job_queue import Job
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
```
|
||||
|
||||
### Step 4: Test Your Command
|
||||
|
||||
Run the bot and test your command:
|
||||
|
||||
```bash
|
||||
python bot.py
|
||||
```
|
||||
|
||||
Then in Discord:
|
||||
```
|
||||
ttr!my-new-command test arguments
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Feature Module
|
||||
|
||||
If your commands don't fit existing modules, create a new one:
|
||||
|
||||
### Step 1: Create the Module File
|
||||
|
||||
Create `commands/your_feature.py`:
|
||||
|
||||
```python
|
||||
"""
|
||||
commands/your_feature.py
|
||||
========================
|
||||
|
||||
Description of what this module handles.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from discord.ext import commands
|
||||
from discord_utils import require_comfy_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_your_feature_commands(bot, config):
|
||||
"""
|
||||
Register your feature commands with the bot.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bot : commands.Bot
|
||||
The Discord bot instance.
|
||||
config : BotConfig
|
||||
The bot configuration object.
|
||||
"""
|
||||
|
||||
@bot.command(name="feature-command")
|
||||
@require_comfy_client
|
||||
async def feature_command(ctx: commands.Context, *, args: str = "") -> None:
|
||||
"""Command description."""
|
||||
await ctx.reply("Feature command executed!", mention_author=False)
|
||||
|
||||
@bot.command(name="another-command", aliases=["ac"])
|
||||
async def another_command(ctx: commands.Context) -> None:
|
||||
"""Another command description."""
|
||||
await ctx.reply("Another command!", mention_author=False)
|
||||
```
|
||||
|
||||
### Step 2: Register in commands/__init__.py
|
||||
|
||||
Edit `commands/__init__.py` to import and register your module:
|
||||
|
||||
```python
|
||||
from .generation import setup_generation_commands
|
||||
from .workflow import setup_workflow_commands
|
||||
from .upload import setup_upload_commands
|
||||
from .history import setup_history_commands
|
||||
from .workflow_changes import setup_workflow_changes_commands
|
||||
from .your_feature import setup_your_feature_commands # ADD THIS
|
||||
|
||||
|
||||
def register_all_commands(bot, config):
|
||||
"""Register all bot commands."""
|
||||
setup_generation_commands(bot, config)
|
||||
setup_workflow_commands(bot)
|
||||
setup_upload_commands(bot)
|
||||
setup_history_commands(bot)
|
||||
setup_workflow_changes_commands(bot)
|
||||
setup_your_feature_commands(bot, config) # ADD THIS
|
||||
```
|
||||
|
||||
### Step 3: Update Documentation
|
||||
|
||||
Add your module to `CLAUDE.md`:
|
||||
|
||||
```markdown
|
||||
### File Structure
|
||||
|
||||
```
|
||||
commands/
|
||||
├── __init__.py
|
||||
├── generation.py
|
||||
├── workflow.py
|
||||
├── upload.py
|
||||
├── history.py
|
||||
├── workflow_changes.py
|
||||
└── your_feature.py # Your new module
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding Configuration Options
|
||||
|
||||
Configuration is centralized in `config.py`. Here's how to add new options:
|
||||
|
||||
### Step 1: Add Constants (if needed)
|
||||
|
||||
Edit `config.py` and add constants in the appropriate section:
|
||||
|
||||
```python
|
||||
# ========================================
|
||||
# Your Feature Constants
|
||||
# ========================================
|
||||
|
||||
MY_FEATURE_DEFAULT_VALUE = 42
|
||||
"""Default value for my feature."""
|
||||
|
||||
MY_FEATURE_MAX_LIMIT = 100
|
||||
"""Maximum limit for my feature."""
|
||||
```
|
||||
|
||||
### Step 2: Add to BotConfig (if environment variable)
|
||||
|
||||
If your config comes from environment variables, add it to `BotConfig`:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class BotConfig:
|
||||
"""Configuration container for the Discord ComfyUI bot."""
|
||||
|
||||
discord_bot_token: str
|
||||
comfy_server: str
|
||||
comfy_output_path: str
|
||||
comfy_history_limit: int
|
||||
workflow_file: Optional[str] = None
|
||||
my_feature_enabled: bool = False # ADD THIS
|
||||
my_feature_value: int = MY_FEATURE_DEFAULT_VALUE # ADD THIS
|
||||
```
|
||||
|
||||
### Step 3: Load in from_env()
|
||||
|
||||
Add loading logic in `BotConfig.from_env()`:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def from_env(cls) -> BotConfig:
|
||||
"""Create a BotConfig instance by loading from environment."""
|
||||
# ... existing code ...
|
||||
|
||||
# Load your feature config
|
||||
my_feature_enabled = os.getenv("MY_FEATURE_ENABLED", "false").lower() == "true"
|
||||
|
||||
try:
|
||||
my_feature_value = int(os.getenv("MY_FEATURE_VALUE", str(MY_FEATURE_DEFAULT_VALUE)))
|
||||
except ValueError:
|
||||
my_feature_value = MY_FEATURE_DEFAULT_VALUE
|
||||
|
||||
return cls(
|
||||
# ... existing parameters ...
|
||||
my_feature_enabled=my_feature_enabled,
|
||||
my_feature_value=my_feature_value,
|
||||
)
|
||||
```
|
||||
|
||||
### Step 4: Use in Your Commands
|
||||
|
||||
Access config in your commands:
|
||||
|
||||
```python
|
||||
def setup_your_feature_commands(bot, config):
|
||||
@bot.command(name="feature")
|
||||
async def feature_command(ctx: commands.Context):
|
||||
if not config.my_feature_enabled:
|
||||
await ctx.reply("Feature is disabled!", mention_author=False)
|
||||
return
|
||||
|
||||
value = config.my_feature_value
|
||||
await ctx.reply(f"Feature value: {value}", mention_author=False)
|
||||
```
|
||||
|
||||
### Step 5: Document the Environment Variable
|
||||
|
||||
Update `CLAUDE.md` and add to `.env.example` (if you create one):
|
||||
|
||||
```bash
|
||||
# Feature Configuration
|
||||
MY_FEATURE_ENABLED=true
|
||||
MY_FEATURE_VALUE=42
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Working with Workflows
|
||||
|
||||
The bot has separate concerns for workflows:
|
||||
|
||||
- **WorkflowManager** (`workflow_manager.py`) - Template storage and node manipulation
|
||||
- **WorkflowStateManager** (`workflow_state.py`) - Runtime state (prompt, negative_prompt, input_image)
|
||||
- **ComfyClient** (`comfy_client.py`) - Uses both managers to generate images
|
||||
|
||||
### Adding New Workflow Node Types
|
||||
|
||||
If you need to manipulate new types of nodes in workflows:
|
||||
|
||||
#### Step 1: Add Method to WorkflowManager
|
||||
|
||||
Edit `workflow_manager.py`:
|
||||
|
||||
```python
|
||||
def find_and_replace_my_node(
|
||||
self, workflow: Dict[str, Any], my_value: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Find and replace my custom node type.
|
||||
|
||||
This searches for nodes of a specific class_type and updates their inputs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
workflow : Dict[str, Any]
|
||||
The workflow definition to modify.
|
||||
my_value : str
|
||||
The value to inject.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dict[str, Any]
|
||||
The modified workflow.
|
||||
"""
|
||||
for node_id, node in workflow.items():
|
||||
if node.get("class_type") == "MyCustomNodeType" and node.get("inputs"):
|
||||
# Check metadata for specific node identification
|
||||
meta = node.get("_meta", {})
|
||||
if "My Custom Node" in meta.get("title", ""):
|
||||
workflow[node_id]["inputs"]["my_input"] = my_value
|
||||
logger.debug("Replaced my_value in node %s", node_id)
|
||||
|
||||
return workflow
|
||||
```
|
||||
|
||||
#### Step 2: Add to apply_state_changes()
|
||||
|
||||
Update `apply_state_changes()` to include your new manipulation:
|
||||
|
||||
```python
|
||||
def apply_state_changes(
|
||||
self,
|
||||
workflow: Dict[str, Any],
|
||||
prompt: Optional[str] = None,
|
||||
negative_prompt: Optional[str] = None,
|
||||
input_image: Optional[str] = None,
|
||||
my_custom_value: Optional[str] = None, # ADD THIS
|
||||
randomize_seed: bool = True,
|
||||
) -> Dict[str, Any]:
|
||||
"""Apply multiple state changes to a workflow in one pass."""
|
||||
if randomize_seed:
|
||||
workflow = self.find_and_replace_seed(workflow)
|
||||
|
||||
if prompt is not None:
|
||||
workflow = self.find_and_replace_prompt(workflow, prompt)
|
||||
|
||||
if negative_prompt is not None:
|
||||
workflow = self.find_and_replace_negative_prompt(workflow, negative_prompt)
|
||||
|
||||
if input_image is not None:
|
||||
workflow = self.find_and_replace_input_image(workflow, input_image)
|
||||
|
||||
# ADD THIS
|
||||
if my_custom_value is not None:
|
||||
workflow = self.find_and_replace_my_node(workflow, my_custom_value)
|
||||
|
||||
return workflow
|
||||
```
|
||||
|
||||
#### Step 3: Add State to WorkflowStateManager
|
||||
|
||||
Edit `workflow_state.py` to track the new state:
|
||||
|
||||
```python
|
||||
def __init__(self, state_file: Optional[str] = None):
|
||||
"""Initialize the workflow state manager."""
|
||||
self._state: Dict[str, Any] = {
|
||||
"prompt": None,
|
||||
"negative_prompt": None,
|
||||
"input_image": None,
|
||||
"my_custom_value": None, # ADD THIS
|
||||
}
|
||||
# ... rest of init ...
|
||||
|
||||
def set_my_custom_value(self, value: str) -> None:
|
||||
"""Set the custom value."""
|
||||
self._state["my_custom_value"] = value
|
||||
if self._state_file:
|
||||
try:
|
||||
self.save_to_file()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_my_custom_value(self) -> Optional[str]:
|
||||
"""Get the custom value."""
|
||||
return self._state.get("my_custom_value")
|
||||
```
|
||||
|
||||
#### Step 4: Use in ComfyClient
|
||||
|
||||
The ComfyClient will automatically use your new state if you update `generate_image_with_workflow()`:
|
||||
|
||||
```python
|
||||
async def generate_image_with_workflow(self) -> tuple[List[bytes], List[dict[str, Any]], str]:
|
||||
# ... existing code ...
|
||||
|
||||
# Get current state changes
|
||||
changes = self.state_manager.get_changes()
|
||||
prompt = changes.get("prompt")
|
||||
negative_prompt = changes.get("negative_prompt")
|
||||
input_image = changes.get("input_image")
|
||||
my_custom_value = changes.get("my_custom_value") # ADD THIS
|
||||
|
||||
# Apply changes using WorkflowManager
|
||||
workflow = self.workflow_manager.apply_state_changes(
|
||||
workflow,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
input_image=input_image,
|
||||
my_custom_value=my_custom_value, # ADD THIS
|
||||
randomize_seed=True,
|
||||
)
|
||||
|
||||
# ... rest of method ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Command Design
|
||||
|
||||
1. **Use descriptive names**: `ttr!generate` is better than `ttr!gen` (but provide aliases)
|
||||
2. **Validate inputs early**: Check arguments before starting long operations
|
||||
3. **Provide clear feedback**: Tell users what's happening and when it's done
|
||||
4. **Handle errors gracefully**: Catch exceptions and show user-friendly messages
|
||||
5. **Use decorators**: `@require_comfy_client` eliminates boilerplate
|
||||
|
||||
### Code Organization
|
||||
|
||||
1. **One responsibility per module**: Don't mix unrelated commands
|
||||
2. **Keep functions small**: If a function is > 50 lines, consider splitting it
|
||||
3. **Use type hints**: Help future developers understand your code
|
||||
4. **Document with docstrings**: Explain what, why, and how
|
||||
|
||||
### Discord Best Practices
|
||||
|
||||
1. **Use `mention_author=False`**: Prevents spam from @mentions
|
||||
2. **Use `delete_after=X`**: For temporary status messages
|
||||
3. **Use `ephemeral=True`**: For interaction responses (buttons/modals)
|
||||
4. **Limit file attachments**: Discord has a 4-file limit (use `MAX_IMAGES_PER_RESPONSE`)
|
||||
|
||||
### Performance
|
||||
|
||||
1. **Use job queue for long operations**: Queue generation requests
|
||||
2. **Use typing indicator**: `async with ctx.typing():` shows bot is working
|
||||
3. **Batch operations**: Don't send 10 separate messages when 1 will do
|
||||
4. **Close resources**: Always close aiohttp sessions, file handles
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Labeled Argument Parsing
|
||||
|
||||
For commands with `key:value` syntax:
|
||||
|
||||
```python
|
||||
from discord_utils import parse_labeled_args
|
||||
from config import ARG_PROMPT_KEY, ARG_TYPE_KEY
|
||||
|
||||
@bot.command(name="my-command")
|
||||
async def my_command(ctx: commands.Context, *, args: str = ""):
|
||||
# Parse labeled arguments
|
||||
parsed = parse_labeled_args(args, [ARG_PROMPT_KEY, ARG_TYPE_KEY])
|
||||
|
||||
prompt = parsed.get("prompt") # None if not provided
|
||||
image_type = parsed.get("type") or "input" # Default to "input"
|
||||
|
||||
if not prompt:
|
||||
await ctx.reply("Please provide a prompt!", mention_author=False)
|
||||
return
|
||||
```
|
||||
|
||||
### Pattern 2: Queued Job Execution
|
||||
|
||||
For long-running operations:
|
||||
|
||||
```python
|
||||
from job_queue import Job
|
||||
|
||||
@bot.command(name="long-operation")
|
||||
@require_comfy_client
|
||||
async def long_operation(ctx: commands.Context, *, args: str = ""):
|
||||
try:
|
||||
# Define the job function
|
||||
async def _run_job():
|
||||
async with ctx.typing():
|
||||
result = await bot.comfy.some_long_operation(args)
|
||||
await ctx.reply(f"Done! Result: {result}", mention_author=False)
|
||||
|
||||
# Submit to queue
|
||||
position = await bot.jobq.submit(
|
||||
Job(
|
||||
label=f"long-operation:{ctx.author.id}",
|
||||
run=_run_job,
|
||||
)
|
||||
)
|
||||
|
||||
await ctx.reply(
|
||||
f"Queued ✅ (position: {position})",
|
||||
mention_author=False,
|
||||
delete_after=2.0,
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to queue long operation")
|
||||
await ctx.reply(
|
||||
f"An error occurred: {type(exc).__name__}: {exc}",
|
||||
mention_author=False,
|
||||
)
|
||||
```
|
||||
|
||||
### Pattern 3: File Attachments
|
||||
|
||||
For uploading files to ComfyUI:
|
||||
|
||||
```python
|
||||
@bot.command(name="upload-and-process")
|
||||
@require_comfy_client
|
||||
async def upload_and_process(ctx: commands.Context):
|
||||
if not ctx.message.attachments:
|
||||
await ctx.reply("Please attach a file!", mention_author=False)
|
||||
return
|
||||
|
||||
for attachment in ctx.message.attachments:
|
||||
try:
|
||||
# Download attachment
|
||||
data = await attachment.read()
|
||||
|
||||
# Upload to ComfyUI
|
||||
result = await bot.comfy.upload_image(
|
||||
data,
|
||||
attachment.filename,
|
||||
image_type="input",
|
||||
)
|
||||
|
||||
# Process the uploaded file
|
||||
filename = result.get("name")
|
||||
await ctx.reply(f"Uploaded: {filename}", mention_author=False)
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to process attachment")
|
||||
await ctx.reply(
|
||||
f"Failed: {attachment.filename}: {exc}",
|
||||
mention_author=False,
|
||||
)
|
||||
```
|
||||
|
||||
### Pattern 4: Interactive UI (Buttons)
|
||||
|
||||
For adding buttons to messages:
|
||||
|
||||
```python
|
||||
from discord.ui import View, Button
|
||||
import discord
|
||||
|
||||
class MyView(View):
|
||||
def __init__(self, data: str):
|
||||
super().__init__(timeout=None)
|
||||
self.data = data
|
||||
|
||||
@discord.ui.button(label="Click Me", style=discord.ButtonStyle.primary)
|
||||
async def button_callback(
|
||||
self, interaction: discord.Interaction, button: discord.ui.Button
|
||||
):
|
||||
# Handle button click
|
||||
await interaction.response.send_message(
|
||||
f"You clicked! Data: {self.data}",
|
||||
ephemeral=True,
|
||||
)
|
||||
|
||||
@bot.command(name="interactive")
|
||||
async def interactive(ctx: commands.Context):
|
||||
view = MyView(data="example")
|
||||
await ctx.reply("Click the button:", view=view, mention_author=False)
|
||||
```
|
||||
|
||||
### Pattern 5: Using Configuration
|
||||
|
||||
Access bot configuration in commands:
|
||||
|
||||
```python
|
||||
def setup_my_commands(bot, config):
|
||||
@bot.command(name="check-config")
|
||||
async def check_config(ctx: commands.Context):
|
||||
# Access config values
|
||||
server = config.comfy_server
|
||||
output_path = config.comfy_output_path
|
||||
|
||||
await ctx.reply(
|
||||
f"Server: {server}\nOutput: {output_path}",
|
||||
mention_author=False,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Changes
|
||||
|
||||
### Manual Testing Checklist
|
||||
|
||||
1. **Start the bot**: `python bot.py`
|
||||
- Verify no import errors
|
||||
- Check configuration loads correctly
|
||||
- Confirm all commands register
|
||||
|
||||
2. **Test basic functionality**:
|
||||
```
|
||||
ttr!test
|
||||
ttr!help
|
||||
ttr!your-new-command
|
||||
```
|
||||
|
||||
3. **Test error handling**:
|
||||
- Run command with missing arguments
|
||||
- Run command with invalid arguments
|
||||
- Test when ComfyUI is unavailable (if applicable)
|
||||
|
||||
4. **Test edge cases**:
|
||||
- Very long inputs
|
||||
- Special characters
|
||||
- Concurrent command execution
|
||||
- Commands while queue is full
|
||||
|
||||
### Syntax Validation
|
||||
|
||||
Check for syntax errors without running:
|
||||
|
||||
```bash
|
||||
python -m py_compile bot.py
|
||||
python -m py_compile commands/your_feature.py
|
||||
```
|
||||
|
||||
### Check Imports
|
||||
|
||||
Verify all imports work:
|
||||
|
||||
```bash
|
||||
python -c "from commands.your_feature import setup_your_feature_commands; print('OK')"
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
Follow these conventions:
|
||||
|
||||
- **Indentation**: 4 spaces (no tabs)
|
||||
- **Line length**: Max 100 characters (documentation can be longer)
|
||||
- **Docstrings**: Use Google style or NumPy style (match existing code)
|
||||
- **Imports**: Group stdlib, third-party, local (separated by blank lines)
|
||||
- **Type hints**: Use modern syntax (`dict[str, Any]` not `Dict[str, Any]`)
|
||||
|
||||
---
|
||||
|
||||
## Example: Complete Feature Addition
|
||||
|
||||
Here's a complete example adding a "status" command:
|
||||
|
||||
### 1. Create commands/status.py
|
||||
|
||||
```python
|
||||
"""
|
||||
commands/status.py
|
||||
==================
|
||||
|
||||
Bot status and diagnostics commands.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import asyncio
|
||||
from discord.ext import commands
|
||||
from discord_utils import require_comfy_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_status_commands(bot, config):
|
||||
"""Register status commands with the bot."""
|
||||
|
||||
@bot.command(name="status", aliases=["s", "stat"])
|
||||
async def status_command(ctx: commands.Context) -> None:
|
||||
"""
|
||||
Show bot status and queue information.
|
||||
|
||||
Usage:
|
||||
ttr!status
|
||||
|
||||
Displays:
|
||||
- Bot connection status
|
||||
- ComfyUI connection status
|
||||
- Current queue size
|
||||
- Configuration info
|
||||
"""
|
||||
# Check bot status
|
||||
latency_ms = round(bot.latency * 1000)
|
||||
|
||||
# Check queue
|
||||
if hasattr(bot, "jobq"):
|
||||
queue_size = await bot.jobq.get_queue_size()
|
||||
else:
|
||||
queue_size = 0
|
||||
|
||||
# Check ComfyUI
|
||||
comfy_status = "✅ Connected" if hasattr(bot, "comfy") else "❌ Not configured"
|
||||
|
||||
# Build status message
|
||||
status_msg = [
|
||||
"**Bot Status**",
|
||||
f"• Latency: {latency_ms}ms",
|
||||
f"• Queue size: {queue_size}",
|
||||
f"• ComfyUI: {comfy_status}",
|
||||
f"• Server: {config.comfy_server}",
|
||||
]
|
||||
|
||||
await ctx.reply("\n".join(status_msg), mention_author=False)
|
||||
|
||||
@bot.command(name="ping")
|
||||
async def ping_command(ctx: commands.Context) -> None:
|
||||
"""
|
||||
Check bot responsiveness.
|
||||
|
||||
Usage:
|
||||
ttr!ping
|
||||
"""
|
||||
latency_ms = round(bot.latency * 1000)
|
||||
await ctx.reply(f"🏓 Pong! Latency: {latency_ms}ms", mention_author=False)
|
||||
```
|
||||
|
||||
### 2. Register in commands/__init__.py
|
||||
|
||||
```python
|
||||
from .status import setup_status_commands
|
||||
|
||||
def register_all_commands(bot, config):
|
||||
# ... existing registrations ...
|
||||
setup_status_commands(bot, config)
|
||||
```
|
||||
|
||||
### 3. Update CLAUDE.md
|
||||
|
||||
```markdown
|
||||
- **commands/status.py** - Bot status and diagnostics commands
|
||||
```
|
||||
|
||||
### 4. Test
|
||||
|
||||
```bash
|
||||
python bot.py
|
||||
```
|
||||
|
||||
In Discord:
|
||||
```
|
||||
ttr!status
|
||||
ttr!ping
|
||||
ttr!s (alias test)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you're stuck:
|
||||
|
||||
1. **Check CLAUDE.md**: Architecture and patterns documented there
|
||||
2. **Read existing commands**: See how similar features are implemented
|
||||
3. **Check logs**: Run bot and check console output for errors
|
||||
4. **Test incrementally**: Add small pieces and test frequently
|
||||
|
||||
Remember: The refactored architecture makes adding features straightforward. Follow the patterns, and your code will fit right in! 🚀
|
||||
Reference in New Issue
Block a user