From a5c8bb57c714b8d27dca103b3053653cb695af94 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 21 Nov 2024 10:17:44 -0500 Subject: [PATCH] improve api scripts resiliency Signed-off-by: Vladimir Mandic --- .eslintrc.json | 1 + cli/api-control.js | 50 ++++++++++++++++++++++++++++++++++++ modules/api/generate.py | 10 +++++--- modules/api/script.py | 3 ++- package.json | 3 ++- scripts/prompts_from_file.py | 8 ++++-- scripts/pulid_ext.py | 9 +++++-- 7 files changed, 74 insertions(+), 10 deletions(-) create mode 100755 cli/api-control.js diff --git a/.eslintrc.json b/.eslintrc.json index 53691dbd9..62feb13a5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,6 +5,7 @@ "plugins": ["html", "json"], "extends": [ "plugin:json/recommended", + "plugin:node/recommended", "eslint:recommended", "airbnb-base" ], diff --git a/cli/api-control.js b/cli/api-control.js new file mode 100755 index 000000000..d7d36f83e --- /dev/null +++ b/cli/api-control.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +// simple nodejs script to test sdnext api + +const fs = require('fs'); +const process = require('process'); + +const sd_url = process.env.SDAPI_URL || 'http://127.0.0.1:7860'; +const sd_username = process.env.SDAPI_USR; +const sd_password = process.env.SDAPI_PWD; +const sd_options = { + // first pass + prompt: 'beautiful lady, in the steampunk style', + negative_prompt: 'foggy, blurry', + seed: -1, + steps: 20, + batch_size: 1, + n_iter: 1, + cfg_scale: 6, + width: 1280, + height: 800, + // api return options + save_images: false, + send_images: true, + script_name: 'pulid', +}; + +async function main() { + const method = 'POST'; + const headers = new Headers(); + const body = JSON.stringify(sd_options); + headers.set('Content-Type', 'application/json'); + if (sd_username && sd_password) headers.set({ Authorization: `Basic ${btoa('sd_username:sd_password')}` }); + const res = await fetch(`${sd_url}/sdapi/v1/txt2img`, { method, headers, body }); + + if (res.status !== 200) { + console.log('Error', res.status); + } else { + const json = await res.json(); + console.log('result:', json.info); + for (const i in json.images) { // eslint-disable-line guard-for-in + const file = `/tmp/test-${i}.jpg`; + const data = atob(json.images[i]) + fs.writeFileSync(file, data, 'binary'); + console.log('image saved:', file); + } + } +} + +main(); diff --git a/modules/api/generate.py b/modules/api/generate.py index d940036fc..deee8db3d 100644 --- a/modules/api/generate.py +++ b/modules/api/generate.py @@ -116,12 +116,13 @@ class APIGenerate(): p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end(api=False) - if processed.images is None or len(processed.images) == 0: + if processed is None or processed.images is None or len(processed.images) == 0: b64images = [] else: b64images = list(map(helpers.encode_pil_to_base64, processed.images)) if send_images else [] self.sanitize_b64(txt2imgreq) - return models.ResTxt2Img(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) + info = processed.js() if processed else '' + return models.ResTxt2Img(images=b64images, parameters=vars(txt2imgreq), info=info) def post_img2img(self, img2imgreq: models.ReqImg2Img): self.prepare_face_module(img2imgreq) @@ -165,7 +166,7 @@ class APIGenerate(): p.script_args = tuple(script_args) # Need to pass args as tuple here processed = process_images(p) shared.state.end(api=False) - if processed.images is None or len(processed.images) == 0: + if processed is None or processed.images is None or len(processed.images) == 0: b64images = [] else: b64images = list(map(helpers.encode_pil_to_base64, processed.images)) if send_images else [] @@ -173,4 +174,5 @@ class APIGenerate(): img2imgreq.init_images = None img2imgreq.mask = None self.sanitize_b64(img2imgreq) - return models.ResImg2Img(images=b64images, parameters=vars(img2imgreq), info=processed.js()) + info = processed.js() if processed else '' + return models.ResImg2Img(images=b64images, parameters=vars(img2imgreq), info=info) diff --git a/modules/api/script.py b/modules/api/script.py index 2c0814ef0..4c506005e 100644 --- a/modules/api/script.py +++ b/modules/api/script.py @@ -81,7 +81,8 @@ def init_script_args(p, request, default_script_args, selectable_scripts, select script_args = default_script_args.copy() # position 0 in script_arg is the idx+1 of the selectable script that is going to be run when using scripts.scripts_*2img.run() if selectable_scripts: - script_args[selectable_scripts.args_from:selectable_scripts.args_to] = request.script_args + for idx in range(len(request.script_args)): + script_args[selectable_scripts.args_from + idx] = request.script_args[idx] script_args[0] = selectable_script_idx + 1 # Now check for always on scripts if request.alwayson_scripts and (len(request.alwayson_scripts) > 0): diff --git a/package.json b/package.json index b30d3f87d..c6657a1f3 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint-plugin-css": "^0.9.2", "eslint-plugin-html": "^8.1.1", "eslint-plugin-json": "^3.1.0", - "eslint-plugin-markdown": "^4.0.1" + "eslint-plugin-markdown": "^4.0.1", + "inkjet": "^3.0.0" } } diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 24dbc0b86..eac66b418 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -86,7 +86,11 @@ def load_prompt_file(file): if file is None: return None, gr.update(), gr.update(lines=7) else: - lines = [x.strip() for x in file.decode('utf8', errors='ignore').split("\n")] + try: + lines = [x.strip() for x in file.decode('utf8', errors='ignore').split("\n")] + except Exception as e: + log.error(f"Prompt file: {e}") + lines = '' return None, "\n".join(lines), gr.update(lines=7) @@ -100,7 +104,7 @@ class Script(scripts.Script): with gr.Row(): checkbox_iterate = gr.Checkbox(label="Iterate seed per line", value=False, elem_id=self.elem_id("checkbox_iterate")) checkbox_iterate_batch = gr.Checkbox(label="Use same seed", value=False, elem_id=self.elem_id("checkbox_iterate_batch")) - prompt_txt = gr.Textbox(label="Prompts", lines=2, elem_id=self.elem_id("prompt_txt")) + prompt_txt = gr.Textbox(label="Prompts", lines=2, elem_id=self.elem_id("prompt_txt"), value='') file = gr.File(label="Upload prompts", type='binary', elem_id=self.elem_id("file")) file.change(fn=load_prompt_file, inputs=[file], outputs=[file, prompt_txt, prompt_txt], show_progress=False) prompt_txt.change(lambda tb: gr.update(lines=7) if ("\n" in tb) else gr.update(lines=2), inputs=[prompt_txt], outputs=[prompt_txt], show_progress=False) diff --git a/scripts/pulid_ext.py b/scripts/pulid_ext.py index 40a21e3f7..800a4645b 100644 --- a/scripts/pulid_ext.py +++ b/scripts/pulid_ext.py @@ -114,7 +114,7 @@ class Script(scripts.Script): ): # pylint: disable=arguments-differ, unused-argument images = [] try: - if len(gallery) == 0: + if gallery is None or isinstance(gallery, str) or len(gallery) == 0: from modules.api.api import decode_base64_to_image images = getattr(p, 'pulid_images', uploaded_images) images = [decode_base64_to_image(image) if isinstance(image, str) else image for image in images] @@ -127,6 +127,12 @@ class Script(scripts.Script): if len(images) == 0: shared.log.error('PuLID: no images') return None + try: + images = [self.pulid.resize(image, 1024) for image in images] + except Exception as e: + shared.log.error(f'PuLID: failed to resize images: {e}') + return None + supported_model_list = ['sdxl'] if shared.sd_model_type not in supported_model_list: shared.log.error(f'PuLID: class={shared.sd_model.__class__.__name__} model={shared.sd_model_type} required={supported_model_list}') @@ -190,7 +196,6 @@ class Script(scripts.Script): self.pulid.attention.NUM_ZERO = zero self.pulid.attention.ORTHO = ortho == 'v1' self.pulid.attention.ORTHO_v2 = ortho == 'v2' - images = [self.pulid.resize(image, 1024) for image in images] shared.sd_model.debug_img_list = [] # get id embedding used for attention