From dae6e601efd1320cf48b7b1fc92f9dc4f5f2adb6 Mon Sep 17 00:00:00 2001 From: HAL-XP Date: Thu, 22 Jan 2026 16:59:23 +0100 Subject: [PATCH] Added Pretty Json functionnality and setting Makes workflow.json readable and helps diffs Updated readme --- README.md | 20 +++- js/pretty_json.js | 247 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 js/pretty_json.js diff --git a/README.md b/README.md index cbe6ecb..d4879c2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # HALXP-Comfy (ComfyUI Extension) -![Version](https://img.shields.io/badge/version-1.0.2-blue) ![ComfyUI](https://img.shields.io/badge/ComfyUI-Compatible-green) +![Version](https://img.shields.io/badge/version-1.0.3-blue) ![ComfyUI](https://img.shields.io/badge/ComfyUI-Compatible-green) **HALXP-Comfy** is a professional UX suite for ComfyUI designed to enhance workflow management. ![App Screenshot](./assets/button.previews.png) ![App Screenshot](./assets/settings.previews.png) -This extension is the starting point for a growing collection of tools. Currently, it features **HALXP Focus**, a way to auto-focus the current running node and , **HALXP RunMonitor** taht lets you run a script on success or error with more enhancements currently in development. +This extension is the starting point for a growing collection of tools. Currently, it features **HALXP Focus**, a way to auto-focus the current running node, **HALXP RunMonitor** that lets you run a script on success or error, and **Pretty JSON Export** for human-readable workflow files, with more enhancements currently in development. **Verified working on:** Legacy UI, ComfyUI V1 (Nodes 2.0), and Portable Distributions. @@ -27,7 +27,7 @@ A smart "Focus" button injected into your main menu bar. ### 2. HALXP RunMonitor A way to run a custom script on workflow success or failure * Go in the Settings under HALXP-Comfy and set a batch file to run or anything you want -* (play a wav, open a website, etc.) separately for sucess and errors - doesn't cover static validation on launch +* (play a wav, open a website, etc.) separately for success and errors - doesn't cover static validation error on launch * ex: With my phone provider (Free Mobile in France), I have an API to send SMS so I can go do other things and get notified when my workflow ends * batch file content example: * start "" "https://smsapi.free-mobile.fr/sendmsg?user=XXXXXXXXXX&pass=YYYYYYYYYYY&msg=Comfy%%20Error%%20!" @@ -35,7 +35,17 @@ A way to run a custom script on workflow success or failure * **Visual Feedback:** Features a status indicator (Green dot = Active) so you always know when Monitor is engaged. * **V1 Compatible:** Works natively with the new Vue.js frontend and PrimeVue menus. -### 3. More Coming Soon +### 3. Pretty JSON Export +Makes workflow JSON exports human-readable with proper indentation for easier version control diffs. +* **Automatic formatting:** All workflow saves and exports are automatically pretty-printed with 2-space indentation +* **No configuration needed:** Works out of the box +* **Manual export functions:** Available in browser console: + - `exportPrettyWorkflow()` - exports the visual workflow JSON + - `exportPrettyApiWorkflow()` - exports the API format JSON +* **Version control friendly:** Makes git diffs readable and reviewable +* **Customizable indentation:** Edit `js/pretty_json.js` to change indent size if needed + +### 4. More Coming Soon HALXP is built as a modular suite. Expect future updates to include additional layout and debugging tools to further improve your ComfyUI experience. ## 📦 Installation @@ -49,4 +59,4 @@ HALXP is built as a modular suite. Expect future updates to include additional l 1. Navigate to your `ComfyUI/custom_nodes/` directory. 2. Clone this repository: ```bash - git clone [https://github.com/YOUR_USERNAME/halxp-comfyui](https://github.com/YOUR_USERNAME/halxp-comfyui) + git clone [https://github.com/HAL-XP/halxp-comfyui](https://github.com/HAL-XP/halxp-comfyui) \ No newline at end of file diff --git a/js/pretty_json.js b/js/pretty_json.js new file mode 100644 index 0000000..44a1d05 --- /dev/null +++ b/js/pretty_json.js @@ -0,0 +1,247 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; + +/** + * ComfyUI Pretty JSON Export Extension + * Makes workflow JSON exports human-readable with proper indentation + * for easier version control diffs. + */ + +const INDENT_SPACES = 2; +const SETTING_ENABLE_ID = "HALXP.PrettyJSON.Enable"; + +// Store original methods +let originalMethods = {}; + +// Track if Pretty JSON formatting is enabled +let isPrettyJsonEnabled = false; + +function prettyStringify(obj, indent = INDENT_SPACES) { + return JSON.stringify(obj, null, indent); +} + +// Override the native JSON.stringify for Blob creation in download contexts +function patchBlobConstructor() { + const OriginalBlob = window.Blob; + + window.Blob = function(parts, options) { + // Only apply formatting if Pretty JSON is enabled + if (isPrettyJsonEnabled && (options?.type === "application/json" || options?.type === "text/json")) { + // Try to parse and re-stringify with formatting + const newParts = parts.map(part => { + if (typeof part === "string") { + try { + const parsed = JSON.parse(part); + return prettyStringify(parsed); + } catch (e) { + return part; + } + } + return part; + }); + return new OriginalBlob(newParts, options); + } + return new OriginalBlob(parts, options); + }; + + // Preserve prototype chain + window.Blob.prototype = OriginalBlob.prototype; +} + +// Hook into the app's workflow serialization +function patchWorkflowSerialization() { + // Patch the graph serialization if available + if (app.graph && app.graph.serialize) { + const originalSerialize = app.graph.serialize.bind(app.graph); + app.graph.serialize = function() { + const result = originalSerialize(); + // Mark that this should be pretty-printed + result._prettyPrint = true; + return result; + }; + } +} + +// Intercept file downloads +function patchDownloadFunctions() { + // Common download pattern used in ComfyUI + const originalCreateElement = document.createElement.bind(document); + + document.createElement = function(tagName, options) { + const element = originalCreateElement(tagName, options); + + if (tagName.toLowerCase() === 'a') { + const originalSetAttribute = element.setAttribute.bind(element); + element.setAttribute = function(name, value) { + if (name === 'href' && value?.startsWith('blob:')) { + // This is likely a download - already handled by Blob patch + } + return originalSetAttribute(name, value); + }; + } + + return element; + }; +} + +// Patch the userdata API calls for workflow saving +function patchApiCalls() { + const originalFetch = window.fetch; + + window.fetch = async function(url, options) { + // Only apply formatting if Pretty JSON is enabled + if (isPrettyJsonEnabled && + typeof url === 'string' && + url.includes('/userdata/') && + url.includes('.json') && + options?.method === 'POST' && + options?.body) { + + try { + let body = options.body; + + // Handle string body + if (typeof body === 'string') { + const parsed = JSON.parse(body); + options = { + ...options, + body: prettyStringify(parsed) + }; + } + // Handle Blob body + else if (body instanceof Blob) { + const text = await body.text(); + try { + const parsed = JSON.parse(text); + options = { + ...options, + body: new Blob([prettyStringify(parsed)], { type: body.type }) + }; + } catch (e) { + // Not JSON, pass through + } + } + } catch (e) { + // Parse error, pass through original + console.debug('[PrettyJSON] Could not format request body:', e); + } + } + + return originalFetch(url, options); + }; +} + +// Add a menu option to export pretty JSON manually +function addExportMenuItem() { + const originalGetCanvasMenuOptions = app.canvas?.getCanvasMenuOptions; + + if (app.canvas && originalGetCanvasMenuOptions) { + app.canvas.getCanvasMenuOptions = function() { + const options = originalGetCanvasMenuOptions.apply(this, arguments); + + // Find the export section or add at the end + options.push(null); // separator + options.push({ + content: "Export Pretty JSON", + callback: () => { + exportPrettyWorkflow(); + } + }); + + return options; + }; + } +} + +// Export workflow as pretty-printed JSON +async function exportPrettyWorkflow() { + try { + const workflow = app.graph.serialize(); + const json = prettyStringify(workflow); + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'workflow_pretty.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + console.log('[PrettyJSON] Exported pretty workflow JSON'); + } catch (e) { + console.error('[PrettyJSON] Export failed:', e); + alert('Failed to export workflow: ' + e.message); + } +} + +// Export API format as pretty JSON +async function exportPrettyApiWorkflow() { + try { + const prompt = await app.graphToPrompt(); + const json = prettyStringify(prompt.output); + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'workflow_api_pretty.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + console.log('[PrettyJSON] Exported pretty API workflow JSON'); + } catch (e) { + console.error('[PrettyJSON] API export failed:', e); + alert('Failed to export API workflow: ' + e.message); + } +} + +// Register the extension +app.registerExtension({ + name: "comfy.prettyJson", + + async setup() { + console.log('[PrettyJSON] Initializing Pretty JSON Export extension...'); + + // Register the setting + app.ui.settings.addSetting({ + id: SETTING_ENABLE_ID, + name: "Enable Pretty JSON", + type: "boolean", + defaultValue: false, + category: ['HALXP-Comfy', 'Pretty JSON', 'Enable'], + tooltip: "Enable Pretty JSON formatting to ease diffs and make it readable.", + onChange: (value) => { + isPrettyJsonEnabled = value; + console.log(`[PrettyJSON] Pretty JSON formatting ${value ? 'enabled' : 'disabled'}`); + } + }); + + // Apply patches (they check isPrettyJsonEnabled internally) + patchBlobConstructor(); + patchApiCalls(); + + // Initialize state from saved setting + setTimeout(() => { + isPrettyJsonEnabled = app.ui.settings.getSettingValue(SETTING_ENABLE_ID, false); + console.log(`[PrettyJSON] Extension loaded - formatting is ${isPrettyJsonEnabled ? 'enabled' : 'disabled'}`); + }, 100); + }, + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + // Can add node-specific hooks here if needed + } +}); + +// Also expose functions globally for manual use in console +window.exportPrettyWorkflow = exportPrettyWorkflow; +window.exportPrettyApiWorkflow = exportPrettyApiWorkflow; + +console.log('[PrettyJSON] Pretty JSON Export extension registered'); +console.log('[PrettyJSON] Enable in Settings > HALXP-Comfy > Pretty JSON'); +console.log('[PrettyJSON] Manual export available via: exportPrettyWorkflow() or exportPrettyApiWorkflow()');