From 9c1a2bb43cc2344b36a2fabbb905308bb529a253 Mon Sep 17 00:00:00 2001 From: Mie Date: Fri, 14 Jun 2024 01:55:32 +0900 Subject: [PATCH 1/9] feat: add support for ComfyUI --- data/default-comfyui-i2i-wf.json | 122 +++++++++++++++++++++++++++++++ data/default-comfyui-t2i-wf.json | 107 +++++++++++++++++++++++++++ src/config.ts | 51 ++++++++++++- src/index.ts | 102 +++++++++++++++++++++++++- 4 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 data/default-comfyui-i2i-wf.json create mode 100644 data/default-comfyui-t2i-wf.json diff --git a/data/default-comfyui-i2i-wf.json b/data/default-comfyui-i2i-wf.json new file mode 100644 index 0000000..d42da6f --- /dev/null +++ b/data/default-comfyui-i2i-wf.json @@ -0,0 +1,122 @@ +{ + "3": { + "inputs": { + "seed": 1, + "steps": 20, + "cfg": 8, + "sampler_name": "euler", + "scheduler": "normal", + "denoise": 0.87, + "model": [ + "14", + 0 + ], + "positive": [ + "6", + 0 + ], + "negative": [ + "7", + 0 + ], + "latent_image": [ + "12", + 0 + ] + }, + "class_type": "KSampler", + "_meta": { + "title": "KSampler" + } + }, + "6": { + "inputs": { + "text": "", + "clip": [ + "14", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "7": { + "inputs": { + "text": "", + "clip": [ + "14", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "8": { + "inputs": { + "samples": [ + "3", + 0 + ], + "vae": [ + "14", + 2 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "9": { + "inputs": { + "filename_prefix": "ComfyUI", + "images": [ + "8", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + }, + "10": { + "inputs": { + "image": "example.png", + "upload": "image" + }, + "class_type": "LoadImage", + "_meta": { + "title": "Load Image" + } + }, + "12": { + "inputs": { + "pixels": [ + "10", + 0 + ], + "vae": [ + "14", + 2 + ] + }, + "class_type": "VAEEncode", + "_meta": { + "title": "VAE Encode" + } + }, + "14": { + "inputs": { + "ckpt_name": "" + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + } +} \ No newline at end of file diff --git a/data/default-comfyui-t2i-wf.json b/data/default-comfyui-t2i-wf.json new file mode 100644 index 0000000..16292cd --- /dev/null +++ b/data/default-comfyui-t2i-wf.json @@ -0,0 +1,107 @@ +{ + "3": { + "inputs": { + "seed": 1, + "steps": 20, + "cfg": 8, + "sampler_name": "euler", + "scheduler": "normal", + "denoise": 0.87, + "model": [ + "14", + 0 + ], + "positive": [ + "6", + 0 + ], + "negative": [ + "7", + 0 + ], + "latent_image": [ + "16", + 0 + ] + }, + "class_type": "KSampler", + "_meta": { + "title": "KSampler" + } + }, + "6": { + "inputs": { + "text": "", + "clip": [ + "14", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "7": { + "inputs": { + "text": "", + "clip": [ + "14", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "8": { + "inputs": { + "samples": [ + "3", + 0 + ], + "vae": [ + "14", + 2 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "9": { + "inputs": { + "filename_prefix": "ComfyUI", + "images": [ + "8", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + }, + "14": { + "inputs": { + "ckpt_name": "" + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + }, + "16": { + "inputs": { + "width": 512, + "height": 800, + "batch_size": 1 + }, + "class_type": "EmptyLatentImage", + "_meta": { + "title": "Empty Latent Image" + } + } +} \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index da554a8..e2e4069 100644 --- a/src/config.ts +++ b/src/config.ts @@ -33,6 +33,7 @@ type Orient = keyof typeof orientMap export const models = Object.keys(modelMap) as Model[] export const orients = Object.keys(orientMap) as Orient[] export const scheduler = ['native', 'karras', 'exponential', 'polyexponential'] as const +export const schedulerComfyUI = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform"] as const export namespace sampler { export const nai = { @@ -87,6 +88,31 @@ export namespace sampler { DDIM_ka: 'DDIM Karras', } + export const comfyui = { + euler: 'Euler', + euler_ancestral: 'Euler ancestral', + heun: 'Heun', + heunpp2: 'Heun++ 2', + dpm_2: 'DPM 2', + dpm_2_ancestral: 'DPM 2 ancestral', + lms: 'LMS', + dpm_fast: 'DPM fast', + dpm_adaptive: 'DPM adaptive', + dpmpp_2s_ancestral: 'DPM++ 2S ancestral', + dpmpp_sde: 'DPM++ SDE', + dpmpp_sde_gpu: 'DPM++ SDE GPU', + dpmpp_2m: 'DPM++ 2M', + dpmpp_2m_sde: 'DPM++ 2M SDE', + dpmpp_2m_sde_gpu: 'DPM++ 2M SDE GPU', + dpmpp_3m_sde: 'DPM++ 3M SDE', + dpmpp_3m_sde_gpu: 'DPM++ 3M SDE GPU', + ddpm: 'DDPM', + lcm: 'LCM', + ddim: 'DDIM', + uni_pc: 'UniPC', + uni_pc_bh2: 'UniPC BH2', + } + export function createSchema(map: Dict) { return Schema.union(Object.entries(map).map(([key, value]) => { return Schema.const(key).description(value) @@ -201,7 +227,7 @@ interface ParamConfig { } export interface Config extends PromptConfig, ParamConfig { - type: 'token' | 'login' | 'naifu' | 'sd-webui' | 'stable-horde' + type: 'token' | 'login' | 'naifu' | 'sd-webui' | 'stable-horde' | 'comfyui' token?: string email?: string password?: string @@ -220,6 +246,8 @@ export interface Config extends PromptConfig, ParamConfig { maxConcurrency?: number pollInterval?: number trustedWorkers?: boolean + workflowText2Image?: string + workflowImage2Image?: string } export const Config = Schema.intersect([ @@ -230,6 +258,7 @@ export const Config = Schema.intersect([ Schema.const('naifu').description('naifu'), Schema.const('sd-webui').description('sd-webui'), Schema.const('stable-horde').description('Stable Horde'), + Schema.const('comfyui').description('ComfyUI'), ]).default('token').description('登录方式。'), }).description('登录设置'), @@ -278,6 +307,12 @@ export const Config = Schema.intersect([ trustedWorkers: Schema.boolean().description('是否只请求可信任工作节点。').default(false), pollInterval: Schema.number().role('time').description('轮询进度间隔时长。').default(Time.second), }), + Schema.object({ + type: Schema.const('comfyui'), + endpoint: Schema.string().description('API 服务器地址。').required(), + headers: Schema.dict(String).role('table').description('要附加的额外请求头。'), + pollInterval: Schema.number().role('time').description('轮询进度间隔时长。').default(Time.second), + }), ]), Schema.object({ @@ -322,6 +357,20 @@ export const Config = Schema.intersect([ type: Schema.const('naifu').required(), sampler: sampler.createSchema(sampler.nai), }), + Schema.object({ + type: Schema.const('comfyui').required(), + sampler: sampler.createSchema(sampler.comfyui).description('默认的采样器。'), + model: Schema.string().description('默认的生成模型的文件名。').required(), + workflowText2Image: Schema.path({ + filters: [{ name: '', extensions: ['.json'] }], + allowCreate: true, + }).description('API格式的图像到图像工作流。'), + workflowImage2Image: Schema.path({ + filters: [{ name: '', extensions: ['.json'] }], + allowCreate: true, + }).description('API格式的图像到图像工作流。'), + scheduler: Schema.union(schedulerComfyUI).description('默认的调度器。').default('normal'), + }), Schema.intersect([ Schema.object({ model: Schema.union(models).loose().description('默认的生成模型。').default('nai-v3'), diff --git a/src/index.ts b/src/index.ts index c3aebba..dc929e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { closestMultiple, download, forceDataPrefix, getImageSize, login, Networ import { } from '@koishijs/translator' import { } from '@koishijs/plugin-help' import AdmZip from 'adm-zip' +import { resolve } from 'path' export * from './config' @@ -301,12 +302,14 @@ export function apply(ctx: Context, config: Config) { return '/api/v2/generate/async' case 'naifu': return '/generate-stream' + case 'comfyui': + return '/prompt' default: return '/ai/generate-image' } })() - const getPayload = () => { + const getPayload = async () => { switch (config.type) { case 'login': case 'token': @@ -392,6 +395,76 @@ export function apply(ctx: Context, config: Config) { r2: true, } } + case 'comfyui': { + const workflowText2Image = config.workflowText2Image ? resolve(ctx.baseDir, config.workflowText2Image) : '../data/default-comfyui-t2i-wf.json' + const workflowImage2Image = config.workflowImage2Image ? resolve(ctx.baseDir, config.workflowImage2Image) : '../data/default-comfyui-i2i-wf.json' + const workflow = image ? workflowImage2Image : workflowText2Image + logger.debug('workflow:', workflow) + const prompt= require(workflow) + + // have to upload image to the comfyui server first + if (image) { + const body = new FormData(); + const capture = /^data:([\w/-]+);base64,(.*)$/.exec(image.dataUrl) + const [, mime,] = capture + + let name = Date.now().toString() + const ext = mime === 'image/jpeg' ? 'jpg' : mime === 'image/png' ? 'png' : ''; + if (ext) name += `.${ext}` + const imageFile = new Blob([image.buffer], {type:mime}) + body.append("image", imageFile, name); + const res = await ctx.http(trimSlash(config.endpoint) + "/upload/image", { + method: 'POST', + headers: { + ...config.headers, + }, + data: body, + }) + if (res.status === 200) { + const data = res.data; + let imagePath = data.name; + if (data.subfolder) imagePath = data.subfolder + "/" + imagePath; + + for (const nodeId in prompt) { + if (prompt[nodeId].class_type === 'LoadImage') { + prompt[nodeId].inputs.image = imagePath + break + } + } + } else { + throw new SessionError('commands.novelai.messages.unknown-error') + } + } + + // only change the first node in the workflow + for (const nodeId in prompt) { + if (prompt[nodeId].class_type === 'KSampler') { + prompt[nodeId].inputs.seed = parameters.seed + prompt[nodeId].inputs.steps = parameters.steps + prompt[nodeId].inputs.cfg = parameters.scale + prompt[nodeId].inputs.sampler_name = options.sampler + prompt[nodeId].inputs.denoise = options.strength ?? config.strength + prompt[nodeId].inputs.scheduler = options.scheduler ?? config.scheduler + const positiveNodeId = prompt[nodeId].inputs.positive[0] + const negativeeNodeId = prompt[nodeId].inputs.negative[0] + const latentImageNodeId = prompt[nodeId].inputs.latent_image[0] + prompt[positiveNodeId].inputs.text = parameters.prompt + prompt[negativeeNodeId].inputs.text = parameters.uc + prompt[latentImageNodeId].inputs.width = parameters.width + prompt[latentImageNodeId].inputs.height = parameters.height + prompt[latentImageNodeId].inputs.batch_size = parameters.n_samples + break + } + } + for (const nodeId in prompt) { + if (prompt[nodeId].class_type === 'CheckpointLoaderSimple') { + prompt[nodeId].inputs.ckpt_name = options.model ?? config.model + break + } + } + logger.debug('prompt:', prompt) + return { "prompt" : prompt } + } } } @@ -418,7 +491,7 @@ export function apply(ctx: Context, config: Config) { ...config.headers, ...getHeaders(), }, - data: getPayload(), + data: await getPayload(), }) if (config.type === 'sd-webui') { @@ -453,6 +526,31 @@ export function apply(ctx: Context, config: Config) { const b64 = Buffer.from(imgRes.data).toString('base64') return forceDataPrefix(b64, imgRes.headers.get('content-type')) } + if (config.type === 'comfyui') { + //get filenames from history + const promptId = res.data.prompt_id + const check = () => ctx.http.get(trimSlash(config.endpoint) + '/history/' + promptId) + .then((res) => res[promptId] && res[promptId].outputs) + const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + let outputs + while (!(outputs = await check())) { + await sleep(config.pollInterval) + } + //get images by filename + const imagesOutput: Buffer[] = []; + for (const nodeId in outputs) { + const nodeOutput = outputs[nodeId]; + if ('images' in nodeOutput) { + for (const image of nodeOutput['images']) { + const urlValues = new URLSearchParams({ filename: image['filename'], subfolder: image['subfolder'], type: image['type'] }).toString(); + const imageData = await ctx.http.get(trimSlash(config.endpoint) + `/view?` + urlValues); + imagesOutput.push(imageData); + } + } + } + //return first image + return forceDataPrefix(Buffer.from(imagesOutput[0]).toString('base64')) + } // event: newImage // id: 1 // data: From ec08cc3730ffff88cba9ed1e7aa3796425aae24f Mon Sep 17 00:00:00 2001 From: MieMieMieeeee <34560903+MieMieMieeeee@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:52:28 +0900 Subject: [PATCH 2/9] fix: correct formatting Co-authored-by: Shigma --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index e2e4069..2d3202c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -33,7 +33,7 @@ type Orient = keyof typeof orientMap export const models = Object.keys(modelMap) as Model[] export const orients = Object.keys(orientMap) as Orient[] export const scheduler = ['native', 'karras', 'exponential', 'polyexponential'] as const -export const schedulerComfyUI = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform"] as const +export const schedulerComfyUI = ['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', 'ddim_uniform'] as const export namespace sampler { export const nai = { From c741c38bd2044692666e3aedf71b57def55a058e Mon Sep 17 00:00:00 2001 From: MieMieMieeeee <34560903+MieMieMieeeee@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:00:10 +0900 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Shigma --- src/config.ts | 4 ++-- src/index.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index 2d3202c..7b101fd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -364,11 +364,11 @@ export const Config = Schema.intersect([ workflowText2Image: Schema.path({ filters: [{ name: '', extensions: ['.json'] }], allowCreate: true, - }).description('API格式的图像到图像工作流。'), + }).description('API 格式的文本到图像工作流。'), workflowImage2Image: Schema.path({ filters: [{ name: '', extensions: ['.json'] }], allowCreate: true, - }).description('API格式的图像到图像工作流。'), + }).description('API 格式的图像到图像工作流。'), scheduler: Schema.union(schedulerComfyUI).description('默认的调度器。').default('normal'), }), Schema.intersect([ diff --git a/src/index.ts b/src/index.ts index dc929e4..d90ea37 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { } from '@koishijs/translator' import { } from '@koishijs/plugin-help' import AdmZip from 'adm-zip' import { resolve } from 'path' +import { readFile } from 'fs/promises' export * from './config' @@ -400,12 +401,12 @@ export function apply(ctx: Context, config: Config) { const workflowImage2Image = config.workflowImage2Image ? resolve(ctx.baseDir, config.workflowImage2Image) : '../data/default-comfyui-i2i-wf.json' const workflow = image ? workflowImage2Image : workflowText2Image logger.debug('workflow:', workflow) - const prompt= require(workflow) + const prompt = JSON.parse(await readFile(workflow, 'utf8')) // have to upload image to the comfyui server first if (image) { const body = new FormData(); - const capture = /^data:([\w/-]+);base64,(.*)$/.exec(image.dataUrl) + const capture = /^data:([\w/.+-]+);base64,(.*)$/.exec(image.dataUrl) const [, mime,] = capture let name = Date.now().toString() @@ -463,7 +464,7 @@ export function apply(ctx: Context, config: Config) { } } logger.debug('prompt:', prompt) - return { "prompt" : prompt } + return { prompt } } } } @@ -527,7 +528,7 @@ export function apply(ctx: Context, config: Config) { return forceDataPrefix(b64, imgRes.headers.get('content-type')) } if (config.type === 'comfyui') { - //get filenames from history + // get filenames from history const promptId = res.data.prompt_id const check = () => ctx.http.get(trimSlash(config.endpoint) + '/history/' + promptId) .then((res) => res[promptId] && res[promptId].outputs) @@ -537,7 +538,7 @@ export function apply(ctx: Context, config: Config) { await sleep(config.pollInterval) } //get images by filename - const imagesOutput: Buffer[] = []; + const imagesOutput: Buffer[] = [] for (const nodeId in outputs) { const nodeOutput = outputs[nodeId]; if ('images' in nodeOutput) { From 5553d3701cf573d7ac5b251207f21d5fd01b1b55 Mon Sep 17 00:00:00 2001 From: Mie Date: Sun, 16 Jun 2024 15:26:40 +0900 Subject: [PATCH 4/9] fix: correct formatting --- src/index.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index d90ea37..5a15923 100644 --- a/src/index.ts +++ b/src/index.ts @@ -405,15 +405,15 @@ export function apply(ctx: Context, config: Config) { // have to upload image to the comfyui server first if (image) { - const body = new FormData(); + const body = new FormData() const capture = /^data:([\w/.+-]+);base64,(.*)$/.exec(image.dataUrl) const [, mime,] = capture let name = Date.now().toString() - const ext = mime === 'image/jpeg' ? 'jpg' : mime === 'image/png' ? 'png' : ''; + const ext = mime === 'image/jpeg' ? 'jpg' : mime === 'image/png' ? 'png' : '' if (ext) name += `.${ext}` const imageFile = new Blob([image.buffer], {type:mime}) - body.append("image", imageFile, name); + body.append("image", imageFile, name) const res = await ctx.http(trimSlash(config.endpoint) + "/upload/image", { method: 'POST', headers: { @@ -422,9 +422,9 @@ export function apply(ctx: Context, config: Config) { data: body, }) if (res.status === 200) { - const data = res.data; - let imagePath = data.name; - if (data.subfolder) imagePath = data.subfolder + "/" + imagePath; + const data = res.data + let imagePath = data.name + if (data.subfolder) imagePath = data.subfolder + "/" + imagePath for (const nodeId in prompt) { if (prompt[nodeId].class_type === 'LoadImage') { @@ -537,15 +537,15 @@ export function apply(ctx: Context, config: Config) { while (!(outputs = await check())) { await sleep(config.pollInterval) } - //get images by filename + // get images by filename const imagesOutput: Buffer[] = [] for (const nodeId in outputs) { - const nodeOutput = outputs[nodeId]; + const nodeOutput = outputs[nodeId] if ('images' in nodeOutput) { for (const image of nodeOutput['images']) { - const urlValues = new URLSearchParams({ filename: image['filename'], subfolder: image['subfolder'], type: image['type'] }).toString(); - const imageData = await ctx.http.get(trimSlash(config.endpoint) + `/view?` + urlValues); - imagesOutput.push(imageData); + const urlValues = new URLSearchParams({ filename: image['filename'], subfolder: image['subfolder'], type: image['type'] }).toString() + const imageData = await ctx.http.get(trimSlash(config.endpoint) + `/view?` + urlValues) + imagesOutput.push(imageData) } } } From 66bdd52441685105d971ea0975f15ee3ec699a49 Mon Sep 17 00:00:00 2001 From: Mie Date: Sun, 16 Jun 2024 15:32:52 +0900 Subject: [PATCH 5/9] fix: resolve file paths for readFile --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5a15923..42d1f61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -397,8 +397,8 @@ export function apply(ctx: Context, config: Config) { } } case 'comfyui': { - const workflowText2Image = config.workflowText2Image ? resolve(ctx.baseDir, config.workflowText2Image) : '../data/default-comfyui-t2i-wf.json' - const workflowImage2Image = config.workflowImage2Image ? resolve(ctx.baseDir, config.workflowImage2Image) : '../data/default-comfyui-i2i-wf.json' + const workflowText2Image = config.workflowText2Image ? resolve(ctx.baseDir, config.workflowText2Image) : resolve(__dirname,'../data/default-comfyui-t2i-wf.json') + const workflowImage2Image = config.workflowImage2Image ? resolve(ctx.baseDir, config.workflowImage2Image) : resolve(__dirname,'../data/default-comfyui-i2i-wf.json') const workflow = image ? workflowImage2Image : workflowText2Image logger.debug('workflow:', workflow) const prompt = JSON.parse(await readFile(workflow, 'utf8')) @@ -549,7 +549,7 @@ export function apply(ctx: Context, config: Config) { } } } - //return first image + // return first image return forceDataPrefix(Buffer.from(imagesOutput[0]).toString('base64')) } // event: newImage From 4cb3b4c4d0c983cc7df9dfbc40d6900a154f3697 Mon Sep 17 00:00:00 2001 From: Mie Date: Sun, 16 Jun 2024 18:14:54 +0900 Subject: [PATCH 6/9] chore: add required to sampler of comfyui --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 7b101fd..3db60f4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -359,7 +359,7 @@ export const Config = Schema.intersect([ }), Schema.object({ type: Schema.const('comfyui').required(), - sampler: sampler.createSchema(sampler.comfyui).description('默认的采样器。'), + sampler: sampler.createSchema(sampler.comfyui).description('默认的采样器。').required(), model: Schema.string().description('默认的生成模型的文件名。').required(), workflowText2Image: Schema.path({ filters: [{ name: '', extensions: ['.json'] }], From 718354672c7eb0e77a01d917508d68882f21060c Mon Sep 17 00:00:00 2001 From: MieMieMieeeee <34560903+MieMieMieeeee@users.noreply.github.com> Date: Mon, 17 Jun 2024 00:37:14 +0900 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: idranme <96647698+idranme@users.noreply.github.com> --- src/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 42d1f61..2e7b6e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -414,7 +414,7 @@ export function apply(ctx: Context, config: Config) { if (ext) name += `.${ext}` const imageFile = new Blob([image.buffer], {type:mime}) body.append("image", imageFile, name) - const res = await ctx.http(trimSlash(config.endpoint) + "/upload/image", { + const res = await ctx.http(trimSlash(config.endpoint) + '/upload/image', { method: 'POST', headers: { ...config.headers, @@ -424,7 +424,7 @@ export function apply(ctx: Context, config: Config) { if (res.status === 200) { const data = res.data let imagePath = data.name - if (data.subfolder) imagePath = data.subfolder + "/" + imagePath + if (data.subfolder) imagePath = data.subfolder + '/' + imagePath for (const nodeId in prompt) { if (prompt[nodeId].class_type === 'LoadImage') { @@ -538,14 +538,15 @@ export function apply(ctx: Context, config: Config) { await sleep(config.pollInterval) } // get images by filename - const imagesOutput: Buffer[] = [] + const imagesOutput: ArrayBuffer[] = [] for (const nodeId in outputs) { const nodeOutput = outputs[nodeId] if ('images' in nodeOutput) { for (const image of nodeOutput['images']) { const urlValues = new URLSearchParams({ filename: image['filename'], subfolder: image['subfolder'], type: image['type'] }).toString() - const imageData = await ctx.http.get(trimSlash(config.endpoint) + `/view?` + urlValues) + const imageData = await ctx.http.get(trimSlash(config.endpoint) + '/view?' + urlValues) imagesOutput.push(imageData) + break } } } From dd6011a9df892c67696655abc9cca3d3dd5d73f4 Mon Sep 17 00:00:00 2001 From: Mie Date: Mon, 17 Jun 2024 00:44:14 +0900 Subject: [PATCH 8/9] feat: support for different image MIME types in comfyui --- src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2e7b6e9..78b4f41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -538,20 +538,20 @@ export function apply(ctx: Context, config: Config) { await sleep(config.pollInterval) } // get images by filename - const imagesOutput: ArrayBuffer[] = [] + const imagesOutput: { data: ArrayBuffer, mime: string }[] = []; for (const nodeId in outputs) { const nodeOutput = outputs[nodeId] if ('images' in nodeOutput) { for (const image of nodeOutput['images']) { const urlValues = new URLSearchParams({ filename: image['filename'], subfolder: image['subfolder'], type: image['type'] }).toString() - const imageData = await ctx.http.get(trimSlash(config.endpoint) + '/view?' + urlValues) - imagesOutput.push(imageData) + const imgRes = await ctx.http(trimSlash(config.endpoint) + '/view?' + urlValues) + imagesOutput.push({data: imgRes.data, mime: imgRes.headers.get('content-type')}) break } } } // return first image - return forceDataPrefix(Buffer.from(imagesOutput[0]).toString('base64')) + return forceDataPrefix(Buffer.from(imagesOutput[0].data).toString('base64'),imagesOutput[0].mime) } // event: newImage // id: 1 From aac203c40b7dece0b682927cd7b95e12a1c764f3 Mon Sep 17 00:00:00 2001 From: Shigma Date: Mon, 17 Jun 2024 06:24:28 +0800 Subject: [PATCH 9/9] Apply suggestions from code review --- src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 78b4f41..66e36d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -545,13 +545,13 @@ export function apply(ctx: Context, config: Config) { for (const image of nodeOutput['images']) { const urlValues = new URLSearchParams({ filename: image['filename'], subfolder: image['subfolder'], type: image['type'] }).toString() const imgRes = await ctx.http(trimSlash(config.endpoint) + '/view?' + urlValues) - imagesOutput.push({data: imgRes.data, mime: imgRes.headers.get('content-type')}) + imagesOutput.push({ data: imgRes.data, mime: imgRes.headers.get('content-type') }) break } } } // return first image - return forceDataPrefix(Buffer.from(imagesOutput[0].data).toString('base64'),imagesOutput[0].mime) + return forceDataPrefix(Buffer.from(imagesOutput[0].data).toString('base64'), imagesOutput[0].mime) } // event: newImage // id: 1