Added Pretty Json functionnality and setting
Makes workflow.json readable and helps diffs Updated readme
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# HALXP-Comfy (ComfyUI Extension)
|
||||
|
||||
 
|
||||
 
|
||||
|
||||
**HALXP-Comfy** is a professional UX suite for ComfyUI designed to enhance workflow management.
|
||||
|
||||

|
||||

|
||||
|
||||
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)
|
||||
@@ -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()');
|
||||
Reference in New Issue
Block a user