diff --git a/__init__.py b/__init__.py index cdc1bfa..75e74d3 100644 --- a/__init__.py +++ b/__init__.py @@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'} REQUIRED_MODULES: Set[str] = set() PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio" -__version__ = "0.21.2" +__version__ = "0.22.0" def load_modules(): print(f"đĻ {PACKAGE_DISPLAY_NAME}: Version: {__version__}") diff --git a/package.json b/package.json index 4760ad6..f2f7266 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ComfyUI_RaykoStudio", - "version": "0.21.2", + "version": "0.22.0", "description": "Set of custom nodes for ComfyUI providing additional image processing capabilities", "author": "Raykosan", "license": "Apache License 2.0", diff --git a/pyproject.toml b/pyproject.toml index c4f7bb2..de206c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ComfyUI_RaykoStudio" description = "Set of custom nodes for ComfyUI providing additional image processing capabilities" -version = "0.21.2" +version = "0.22.0" license = { file = "LICENSE" } dependencies = ["torch>=1.7.0", "numpy>=1.19.0", "Pillow>=8.0.0", "freetype-py>=2.5.1"] diff --git a/settings_manager_api.py b/settings_manager_api.py new file mode 100644 index 0000000..fe120fb --- /dev/null +++ b/settings_manager_api.py @@ -0,0 +1,113 @@ +import os +import sys +import shutil +import datetime +import logging +import re +from aiohttp import web +from server import PromptServer + +logger = logging.getLogger(__name__) + +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +COMFYUI_ROOT = os.path.dirname(os.path.dirname(CURRENT_DIR)) +SETTINGS_FILE = os.path.join(COMFYUI_ROOT, "user", "default", "comfy.settings.json") +BACKUP_DIR = os.path.join(os.path.expanduser("~"), "Documents", "ComfyUI_Settings_Backups") + +os.makedirs(BACKUP_DIR, exist_ok=True) +logger.info("đĻ Settings Manager initialized") + +@PromptServer.instance.routes.get("/rayko_settings_manager/ping") +async def ping(request): + return web.json_response({"status": "ok"}) + +@PromptServer.instance.routes.post("/rayko_settings_manager/save") +async def save_settings(request): + try: + if not os.path.exists(SETTINGS_FILE): + return web.json_response({"error": "Settings file not found"}, status=404) + + try: + data = await request.json() + custom_name = data.get('backup_name', '').strip() + except Exception: + custom_name = '' + + sanitized_name = re.sub(r'[<>:"/\\|?*]', '_', custom_name).rstrip('. ') + + if not sanitized_name: + folder_name = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + else: + folder_name = sanitized_name + counter = 1 + while os.path.exists(os.path.join(BACKUP_DIR, folder_name)): + folder_name = f"{sanitized_name}_{counter}" + counter += 1 + + target_folder = os.path.join(BACKUP_DIR, folder_name) + os.makedirs(target_folder, exist_ok=True) + shutil.copy2(SETTINGS_FILE, target_folder) + + return web.json_response({"success": True, "folder": folder_name}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + +@PromptServer.instance.routes.get("/rayko_settings_manager/list") +async def list_settings(request): + try: + if not os.path.exists(BACKUP_DIR): + return web.json_response({"success": True, "backups": []}) + + backups = [d for d in os.listdir(BACKUP_DIR) if os.path.isdir(os.path.join(BACKUP_DIR, d))] + backups.sort(reverse=True) + return web.json_response({"success": True, "backups": backups}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + +@PromptServer.instance.routes.post("/rayko_settings_manager/restore") +async def restore_settings(request): + try: + data = await request.json() + backup_name = data.get("backup_name") + + if not backup_name: + return web.json_response({"error": "Backup name not specified"}, status=400) + + backup_file = os.path.join(BACKUP_DIR, backup_name, "comfy.settings.json") + + if not os.path.exists(backup_file): + return web.json_response({"error": "Backup file not found"}, status=404) + + shutil.copy2(backup_file, SETTINGS_FILE) + return web.json_response({"success": True}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + +@PromptServer.instance.routes.delete("/rayko_settings_manager/delete/{backup_name}") +async def delete_backup(request): + try: + backup_name = request.match_info.get('backup_name') + + if not backup_name: + return web.json_response({"error": "Backup name not specified"}, status=400) + + backup_path = os.path.join(BACKUP_DIR, backup_name) + + if not os.path.exists(backup_path): + return web.json_response({"error": "Backup not found"}, status=404) + + shutil.rmtree(backup_path) + return web.json_response({"success": True}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + +@PromptServer.instance.routes.post("/rayko_settings_manager/restart") +async def restart_server(request): + try: + os.execv(sys.executable, [sys.executable] + sys.argv) + return web.json_response({"success": True}) + except Exception as e: + return web.json_response({"error": str(e)}, status=500) + +NODE_CLASS_MAPPINGS = {} +NODE_DISPLAY_NAME_MAPPINGS = {} \ No newline at end of file diff --git a/web/settings_manager.js b/web/settings_manager.js new file mode 100644 index 0000000..fced178 --- /dev/null +++ b/web/settings_manager.js @@ -0,0 +1,331 @@ +import { app } from "../../../scripts/app.js"; + +app.registerExtension({ + name: "RaykoStudio.SettingsManager", + + async setup() { + if (app.extensionManager && app.extensionManager.registerSidebarTab) { + app.extensionManager.registerSidebarTab({ + id: "RaykoStudio.SettingsManager", + icon: "pi pi-desktop", + title: "đĻ Settings Manager", + tooltip: "Interface Settings Manager", + type: "custom", + render: (el) => { + renderSettingsPanel(el); + }, + }); + } + }, +}); + +function renderSettingsPanel(container) { + container.innerHTML = ''; + + const style = document.createElement('style'); + style.textContent = ` + .sm-container { padding: 15px; display: flex; flex-direction: column; gap: 20px; min-height: 300px; } + .sm-section { background-color: var(--comfy-input-bg); border: 1px solid var(--border-color); border-radius: 8px; padding: 15px; } + .sm-title { font-size: 16px; font-weight: bold; margin-bottom: 10px; color: var(--fg-color); } + .sm-description { font-size: 12px; color: var(--descrip-text); margin-bottom: 15px; } + .sm-input { width: 100%; padding: 8px 12px; margin-bottom: 5px; background-color: var(--bg-color); border: 1px solid var(--border-color); border-radius: 4px; color: var(--fg-color); font-size: 14px; box-sizing: border-box; } + .sm-input:focus { border-color: var(--primary-color); outline: none; } + .sm-hint { font-size: 11px; color: var(--descrip-text); margin-bottom: 15px; font-style: italic; } + .sm-btn { padding: 10px 20px; background-color: var(--comfy-menu-bg); border: 1px solid var(--border-color); border-radius: 4px; color: var(--fg-color); cursor: pointer; font-size: 14px; width: 100%; transition: all 0.3s ease; } + .sm-btn:hover:not(:disabled) { background-color: var(--comfy-menu-secondary-bg); } + .sm-btn:disabled { opacity: 0.5; cursor: not-allowed; } + .sm-btn-success { background-color: #10b981; color: white; border-color: #10b981; } + .sm-btn-success:hover:not(:disabled) { background-color: #059669; } + .sm-btn-warning { background-color: #f59e0b; color: white; border-color: #f59e0b; animation: smPulse 2s infinite; } + .sm-btn-warning:hover:not(:disabled) { background-color: #d97706; } + @keyframes smPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7); } 50% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } } + .sm-btn-danger { background-color: #dc2626; color: white; border-color: #dc2626; } + .sm-btn-danger:hover:not(:disabled) { background-color: #b91c1c; } + .sm-backup-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; background-color: var(--bg-color); border: 1px solid var(--border-color); border-radius: 4px; margin-bottom: 8px; } + .sm-backup-name { flex: 1; color: var(--fg-color); font-size: 13px; word-break: break-all; margin-right: 10px; } + .sm-btn-small { padding: 5px 10px; font-size: 12px; min-width: 80px; width: auto; } + .sm-status-container { margin-top: auto; padding-top: 10px; min-height: 40px; display: flex; align-items: center; justify-content: center; } + .sm-status { padding: 10px 20px; border-radius: 6px; font-size: 13px; text-align: center; display: none; width: 100%; animation: smFadeIn 0.3s ease; } + @keyframes smFadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } + .sm-status.sm-success { display: block; background-color: #10b981; color: white; } + .sm-status.sm-error { display: block; background-color: #ef4444; color: white; } + .sm-status.sm-info { display: block; background-color: #3b82f6; color: white; } + .sm-hidden { display: none; } + .sm-restore-container { display: flex; gap: 10px; margin-top: 10px; } + `; + container.appendChild(style); + + const mainDiv = document.createElement('div'); + mainDiv.className = 'sm-container'; + + const saveSection = document.createElement('div'); + saveSection.className = 'sm-section'; + saveSection.innerHTML = ` +