@sheriff1980/n8n-nodes-comfyuirunner
Version:
An n8n node to run ComfyUI workflows
142 lines (133 loc) • 5.93 kB
text/typescript
import {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
// We might need uuid for client ID generation later
// import { v4 as uuidv4 } from 'uuid';
export class ComfyUiRunner implements INodeType {
description: INodeTypeDescription = {
displayName: 'ComfyUI Runner',
name: 'comfyUiRunner',
// Changed group to something more appropriate
group: ['External APIs'],
version: 1,
description: 'Runs a ComfyUI workflow via its API', // Updated description
defaults: {
name: 'ComfyUI Runner', // Updated default name
},
inputs: ['main'] as any,
outputs: ['main'] as any,
properties: [
// Property for the ComfyUI API URL
{
displayName: 'ComfyUI URL',
name: 'comfyUiUrl',
type: 'string',
default: 'http://127.0.0.1:8188',
placeholder: 'http://127.0.0.1:8188',
description: 'The base URL of your running ComfyUI instance (e.g., http://localhost:8188)',
required: true,
},
// Property for the Workflow JSON
{
displayName: 'Workflow JSON',
name: 'workflowJson',
type: 'json', // Use 'json' type for better input validation and expression support
default: '{}',
placeholder: 'Paste your ComfyUI API format workflow here',
description: 'The ComfyUI workflow in API JSON format. Use expressions to insert dynamic data.',
required: true,
// Optional: Enhance the JSON editor experience
// typeOptions: {
// editor: 'json',
// language: 'json',
// },
},
],
};
// The execute method will be defined below
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
// Loop through each input item
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
// Get node parameters for the current item
const comfyUiUrl = this.getNodeParameter('comfyUiUrl', itemIndex, '') as string;
// Get the workflow JSON, allowing for expressions
const workflowJsonString = this.getNodeParameter('workflowJson', itemIndex, '{}') as string;
// --- Placeholder for ComfyUI API Interaction ---
// This section will contain the logic to:
// 1. Parse workflowJsonString into a JavaScript object.
// let workflowObject = JSON.parse(workflowJsonString);
// 2. Generate a unique client ID (e.g., using uuid).
// const clientId = uuidv4();
// 3. Establish a WebSocket connection to `${comfyUiUrl}/ws?clientId=${clientId}`.
// - Use a library like 'ws'. Remember to add it as a dependency if needed.
// 4. Send the workflow via a POST request to `${comfyUiUrl}/prompt`.
// - The body should be { prompt: workflowObject, client_id: clientId }.
// - Use `this.helpers.httpRequest` for the POST request.
// 5. Listen for messages on the WebSocket connection:
// - Handle 'status' messages (queue updates).
// - Handle 'execution_start' message.
// - Handle 'executing' messages (current node, check for errors).
// - Handle 'progress' messages (optional).
// - Wait for the 'executed' message which contains output data, including image details (filename, subfolder, type).
// - Close the WebSocket connection when done or on error.
// 6. Once the 'executed' message with image details is received:
// - Construct the image URL: `${comfyUiUrl}/view?filename=${filename}&subfolder=${subfolder}&type=${type}`.
// - Fetch the image data using `this.helpers.httpRequest`. Crucially, set `returnBinaryData: true` and `encoding: 'arraybuffer'`.
// const imageDataBuffer = await this.helpers.httpRequest({ ...options... });
// 7. Prepare the output item for n8n:
// - Create binary data using `this.helpers.prepareBinaryData`.
// const binaryOutput: IBinaryData = await this.helpers.prepareBinaryData(
// Buffer.from(imageDataBuffer), // Convert ArrayBuffer to Buffer
// 'output.png' // Or use the filename from ComfyUI
// );
// - Construct the final INodeExecutionData object.
// const newItem: INodeExecutionData = {
// json: { success: true, message: "Workflow executed", outputFilename: filename }, // Example JSON output
// binary: { data: binaryOutput }, // Attach the binary data under a key (e.g., 'data')
// pairedItem: { item: itemIndex },
// };
// returnData.push(newItem);
// --- End Placeholder ---
// For now, just return a success message and pass through input JSON.
// We will replace this with the actual API logic later.
const newItem: INodeExecutionData = {
json: {
...items[itemIndex].json, // Pass original JSON through
_comfyRunnerStatus: 'Placeholder - API call not implemented yet',
_receivedUrl: comfyUiUrl,
_receivedWorkflow: workflowJsonString, // Show what was received
},
binary: {}, // No binary data yet
pairedItem: { item: itemIndex },
};
returnData.push(newItem);
} catch (error: any) {
// Handle errors for individual items
if (this.continueOnFail()) {
const errorItem: INodeExecutionData = {
json: {
error: error.message,
stack: error.stack, // Include stack trace for debugging
itemIndex: itemIndex,
inputJson: items[itemIndex].json, // Include original input for context
},
binary: {},
pairedItem: { item: itemIndex },
};
returnData.push(errorItem);
continue; // Continue to the next item
}
// If not continuing on fail, re-throw the error to stop the workflow
throw error;
}
}
// Return the processed data
return this.prepareOutputData(returnData);
}
}