WEB_DIRECTORY = "./js" from .smart_prompt_controller import NODE_CLASS_MAPPINGS as A, NODE_DISPLAY_NAME_MAPPINGS as A_NAMES from .smart_profile_switch import NODE_CLASS_MAPPINGS as B, NODE_DISPLAY_NAME_MAPPINGS as B_NAMES from .smart_resize import NODE_CLASS_MAPPINGS as C, NODE_DISPLAY_NAME_MAPPINGS as C_NAMES from .smart_batch_resize import NODE_CLASS_MAPPINGS as D, NODE_DISPLAY_NAME_MAPPINGS as D_NAMES from .smart_resolution_multiplier import NODE_CLASS_MAPPINGS as E, NODE_DISPLAY_NAME_MAPPINGS as E_NAMES NODE_CLASS_MAPPINGS = {**A, **B, **C, **D, **E} NODE_DISPLAY_NAME_MAPPINGS = {**A_NAMES, **B_NAMES, **C_NAMES, **D_NAMES, **E_NAMES} __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] try: import asyncio import concurrent.futures import os import threading from aiohttp import web from server import PromptServer _dialog_lock = threading.Lock() # Must match the literal 'CRAFTKIT_DIALOG_ERROR:' prefix written by the # PowerShell catch block below (kept literal there to avoid brace-escaping # a dense here-string). _DIALOG_ERROR_PREFIX = "CRAFTKIT_DIALOG_ERROR:" async def browse_folder(request): import subprocess, sys if request.remote not in ("127.0.0.1", "::1"): return web.json_response({"ok": False, "error": "forbidden"}, status=403) origin = request.headers.get("Origin") if origin is not None: host = request.headers.get("Host", "") origin_host = origin.split("://", 1)[-1] if origin_host != host: return web.json_response({"ok": False, "error": "forbidden"}, status=403) def _open_dialog(): if sys.platform != "win32": return "" ps = r""" Add-Type -AssemblyName System.Windows.Forms Add-Type @" using System; using System.Runtime.InteropServices; public static class CraftKitFolderPicker { [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] private class FileOpenDialogRCW { } [ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IFileDialog { [PreserveSig] int Show(IntPtr parent); void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec); void SetFileTypeIndex(uint iFileType); void GetFileTypeIndex(out uint piFileType); void Advise(IntPtr pfde, out uint pdwCookie); void Unadvise(uint dwCookie); void SetOptions(uint fos); void GetOptions(out uint fos); void SetDefaultFolder(IntPtr psi); void SetFolder(IntPtr psi); void GetFolder(out IntPtr ppsi); void GetCurrentSelection(out IntPtr ppsi); void SetFileName(string pszName); void GetFileName(out IntPtr pszName); void SetTitle(string pszTitle); void SetOkButtonLabel(string pszText); void SetFileNameLabel(string pszLabel); void GetResult(out IShellItemLocal ppsi); void AddPlace(IntPtr psi, uint fdap); void SetDefaultExtension(string pszDefaultExtension); void Close(int hr); void SetClientGuid(ref Guid guid); void ClearClientData(); void SetFilter(IntPtr pFilter); } [ComImport, Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IShellItemLocal { void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv); void GetParent(out IShellItemLocal ppsi); void GetDisplayName(uint sigdnName, out IntPtr ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItemLocal psi, uint hint, out int piOrder); } public static string ShowDialog(IntPtr owner, string title) { const uint ERROR_CANCELLED = 0x800704C7; var dialog = (IFileDialog)new FileOpenDialogRCW(); dialog.SetOptions(0x20u | 0x40u); // FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM if (!string.IsNullOrEmpty(title)) dialog.SetTitle(title); int hr = dialog.Show(owner); if (hr != 0) { if ((uint)hr == ERROR_CANCELLED) return null; throw new System.Runtime.InteropServices.COMException("IFileDialog.Show failed", hr); } IShellItemLocal item; dialog.GetResult(out item); IntPtr pszPath = IntPtr.Zero; try { item.GetDisplayName(0x80058000u, out pszPath); // SIGDN_FILESYSPATH return Marshal.PtrToStringUni(pszPath); } finally { if (pszPath != IntPtr.Zero) Marshal.FreeCoTaskMem(pszPath); } } } "@ $r = '' $o = New-Object System.Windows.Forms.Form $o.TopMost = $true $o.ShowInTaskbar = $false $o.FormBorderStyle = 'None' $o.Width = 1; $o.Height = 1; $o.Opacity = 0 $o.StartPosition = 'CenterScreen' $o.Add_Shown({ $o.Activate() try { $path = [CraftKitFolderPicker]::ShowDialog($o.Handle, 'Select input folder') if ($path) { $script:r = $path } } catch { # Must match _DIALOG_ERROR_PREFIX in __init__.py. $script:r = 'CRAFTKIT_DIALOG_ERROR:' + $_.Exception.Message } $o.Close() }) $o.ShowDialog() | Out-Null $r """ try: result = subprocess.run( ["powershell", "-NoProfile", "-Command", ps], capture_output=True, text=True, timeout=300 ) except subprocess.TimeoutExpired: return "" return result.stdout.strip() if not _dialog_lock.acquire(blocking=False): return web.json_response({"ok": False, "error": "A folder dialog is already open."}, status=409) try: loop = asyncio.get_running_loop() with concurrent.futures.ThreadPoolExecutor() as pool: folder = await loop.run_in_executor(pool, _open_dialog) finally: _dialog_lock.release() if folder.startswith(_DIALOG_ERROR_PREFIX): return web.json_response({"ok": False, "error": folder.removeprefix(_DIALOG_ERROR_PREFIX)}, status=500) if folder and os.path.isdir(folder): return web.json_response({"ok": True, "path": folder}) return web.json_response({"ok": False, "cancelled": True}) PromptServer.instance.routes.post("/craftkit/browse_folder")(browse_folder) except Exception as e: print(f"[CraftKit] Browse folder endpoint unavailable ({e}); paste input folder paths manually.")