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

147
user_state_registry.py Normal file
View File

@@ -0,0 +1,147 @@
"""
user_state_registry.py
======================
Per-user workflow state + template registry.
Each web-UI user gets their own isolated WorkflowStateManager (persisted to
``user_settings/<user_label>.json``) and workflow template.
New users (no saved file) fall back to the global default workflow template
loaded at startup (WORKFLOW_FILE env var or last-used workflow from the
global Discord state manager).
Discord continues to use the shared global state/workflow manager — this
registry is only used by the web UI layer.
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any, Dict, Optional
from workflow_state import WorkflowStateManager
logger = logging.getLogger(__name__)
_PROJECT_ROOT = Path(__file__).resolve().parent
class UserStateRegistry:
"""
Per-user isolated workflow state and template store.
Parameters
----------
settings_dir : Path
Directory where per-user state files are stored. Created automatically.
default_workflow : Optional[dict]
The global default workflow template. Used when a user has no saved
``last_workflow_file``, or the file no longer exists.
"""
def __init__(
self,
settings_dir: Path,
default_workflow: Optional[dict[str, Any]] = None,
) -> None:
self._settings_dir = settings_dir
self._settings_dir.mkdir(parents=True, exist_ok=True)
self._default_workflow: Optional[dict[str, Any]] = default_workflow
# user_label → WorkflowStateManager
self._managers: Dict[str, WorkflowStateManager] = {}
# user_label → workflow template dict (or None)
self._templates: Dict[str, Optional[dict[str, Any]]] = {}
# ------------------------------------------------------------------
# Default workflow
# ------------------------------------------------------------------
def set_default_workflow(self, template: Optional[dict[str, Any]]) -> None:
"""Update the global fallback workflow (called when bot workflow changes)."""
self._default_workflow = template
# ------------------------------------------------------------------
# User access
# ------------------------------------------------------------------
def get_state_manager(self, user_label: str) -> WorkflowStateManager:
"""Return (or lazily create) the WorkflowStateManager for a user."""
if user_label not in self._managers:
self._init_user(user_label)
return self._managers[user_label]
def get_workflow_template(self, user_label: str) -> Optional[dict[str, Any]]:
"""Return the workflow template for a user, or None if not loaded."""
if user_label not in self._managers:
self._init_user(user_label)
return self._templates.get(user_label)
def set_workflow(
self, user_label: str, template: dict[str, Any], filename: str
) -> None:
"""
Store a workflow template for a user and persist the filename.
Clears existing overrides (matches the behaviour of loading a new
workflow via the global state manager).
"""
if user_label not in self._managers:
self._init_user(user_label)
sm = self._managers[user_label]
sm.clear_overrides()
sm.set_last_workflow_file(filename)
self._templates[user_label] = template
logger.debug(
"UserStateRegistry: set workflow '%s' for user '%s'", filename, user_label
)
# ------------------------------------------------------------------
# Internals
# ------------------------------------------------------------------
def _init_user(self, user_label: str) -> None:
"""
Initialise state manager and workflow template for a new user.
1. Create WorkflowStateManager with per-user state file.
2. If the file recorded a last_workflow_file, try to load it.
3. Fall back to the global default template.
"""
state_file = str(self._settings_dir / f"{user_label}.json")
sm = WorkflowStateManager(state_file=state_file)
self._managers[user_label] = sm
# Try to restore the last workflow this user loaded
last_wf = sm.get_last_workflow_file()
template: Optional[dict[str, Any]] = None
if last_wf:
wf_path = _PROJECT_ROOT / "workflows" / last_wf
if wf_path.exists():
try:
with open(wf_path, "r", encoding="utf-8") as f:
template = json.load(f)
logger.debug(
"UserStateRegistry: restored workflow '%s' for user '%s'",
last_wf,
user_label,
)
except Exception as exc:
logger.warning(
"UserStateRegistry: could not load '%s' for user '%s': %s",
last_wf,
user_label,
exc,
)
else:
logger.debug(
"UserStateRegistry: last workflow '%s' missing for user '%s'; using default",
last_wf,
user_label,
)
if template is None:
template = self._default_workflow
self._templates[user_label] = template