Added Pretty Json functionnality and setting

Makes workflow.json readable and helps diffs
Updated readme
This commit is contained in:
HAL-XP
2026-01-22 16:59:23 +01:00
parent 9bcaffe66d
commit dae6e601ef
2 changed files with 262 additions and 5 deletions
+15 -5
View File
@@ -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)
+247
View File
@@ -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()');