diff --git a/.streamlit/config.toml b/.streamlit/config.toml index bf12097..c534e84 100644 --- a/.streamlit/config.toml +++ b/.streamlit/config.toml @@ -83,7 +83,7 @@ enableXsrfProtection = true # Max size, in megabytes, for files uploaded with the file_uploader. # Default: 200 -maxUploadSize = 10 +maxUploadSize = 20 # Max size, in megabytes, of messages that can be sent via the WebSocket # connection. diff --git a/modules/authenticate.py b/modules/authenticate.py index 6997540..5ba4e51 100644 --- a/modules/authenticate.py +++ b/modules/authenticate.py @@ -262,7 +262,7 @@ def register_user_info(self, form_name: str, location: str = 'main', data: dict= register_user_form.subheader(form_name) new_email = register_user_form.text_input('Email', value=data['email'], help='Please enter a valid email address') - new_username = register_user_form.text_input('Username', value=data['username'], help='Please enter a username') + new_username = register_user_form.text_input('Username', value=data['username'], help='Please enter a username, 0-9, a-z, A-Z, -, _, max length 20') new_name = register_user_form.text_input('Name', value=data['username'],help='Please enter your name') new_password = register_user_form.text_input('Password', type='password') diff --git a/modules/comfyclient.py b/modules/comfyclient.py index 040ff8e..cbf110c 100644 --- a/modules/comfyclient.py +++ b/modules/comfyclient.py @@ -53,6 +53,11 @@ def get_image(self, filename, subfolder, folder_type): if resp.status_code != 200: raise Exception(f"Failed to get image from server, {resp.status_code}") return resp.content + + def get_image_url(self, filename, subfolder, folder_type): + url = f"{self.server_addr}/view?filename={filename}&subfolder={subfolder}&type={folder_type}" + logger.info(f"Getting image url, {url}") + return url def upload_image(self, imagefile, subfolder, type, overwrite): data = {"subfolder": subfolder, "type": type, "overwrite": overwrite} @@ -123,7 +128,7 @@ def dispatch_event(queue, event): # Dispatch executing event with msg["data"]["node"] dispatch_event(queue, {"type": "executing", "data": msg["data"]["node"]}) if msg["data"]["node"] is None: - logger.info("worflow finished, exiting websocket loop") + logger.info("workflow finished, exiting websocket loop") break elif msg_type == "executed": # Dispatch executed event with msg["data"] diff --git a/modules/comfyflow.py b/modules/comfyflow.py index 6b0e7bf..68f72c2 100644 --- a/modules/comfyflow.py +++ b/modules/comfyflow.py @@ -85,7 +85,7 @@ def generate(self): prompt_id = self.comfy_client.gen_images(prompt, queue) st.session_state['preview_prompt_id'] = prompt_id - def get_output_images(self): + def get_outputs(self): # get output images by prompt_id prompt_id = st.session_state['preview_prompt_id'] if prompt_id is None: @@ -93,14 +93,26 @@ def get_output_images(self): history = self.comfy_client.get_history(prompt_id)[prompt_id] for node_id in self.app_json['outputs']: node_output = history['outputs'][node_id] + logger.info(f"Got output from server, {node_id}, {node_output}") if 'images' in node_output: images_output = [] for image in node_output['images']: - image_data = self.comfy_client.get_image(image['filename'], image['subfolder'], image['type']) - images_output.append(image_data) + image_url = self.comfy_client.get_image_url(image['filename'], image['subfolder'], image['type']) + images_output.append(image_url) logger.info(f"Got images from server, {node_id}, {len(images_output)}") - return images_output + return 'images', images_output + elif 'gifs' in node_output: + gifs_output = [] + format = 'gifs' + for gif in node_output['gifs']: + if gif['format'] == 'image/gif' or gif['format'] == 'image/webp': + format = 'images' + gif_url = self.comfy_client.get_image_url(gif['filename'], gif['subfolder'], gif['type']) + gifs_output.append(gif_url) + + logger.info(f"Got gifs from server, {node_id}, {len(gifs_output)}") + return format, gifs_output def create_ui_input(self, node_id, node_inputs): @@ -227,12 +239,15 @@ def create_ui(self, show_header=True): elif event_type == 'executing': node = event['data'] if node is None: - output_image = self.get_output_images() - if output_image is not None: - img_placeholder.image(output_image, use_column_width=True) + type, outputs = self.get_outputs() + if type == 'images' and outputs is not None: + img_placeholder.image(outputs, use_column_width=True) + elif type == 'gifs' and outputs is not None: + for output in outputs: + img_placeholder.markdown(f'', unsafe_allow_html=True) - output_progress.progress(1.0, text="Generate image finished") - logger.info("Generating image finished") + output_progress.progress(1.0, text="Generate finished") + logger.info("Generating finished") st.session_state[f'{app_name}_previewed'] = True break else: @@ -243,9 +258,9 @@ def create_ui(self, show_header=True): img_placeholder.image(preview_image, use_column_width=True, caption="Preview") except Exception as e: logger.warning(f"get progress exception, {e}") - st.warning(f"get progress exception {e}") + # st.warning(f"get progress exception {e}") else: output_image = Image.open('./public/images/output-none.png') - logger.info("default output_image") + logger.info("default output") img_placeholder.image(output_image, use_column_width=True, caption='None Image, Generate it!') \ No newline at end of file diff --git a/modules/new_app.py b/modules/new_app.py index 41ce95b..52f1f48 100644 --- a/modules/new_app.py +++ b/modules/new_app.py @@ -9,6 +9,8 @@ from modules import get_comfyui_object_info, get_workspace_model NODE_SEP = '||' +FAQ_URL = "https://github.com/xingren23/ComfyFlowApp/wiki/FAQ" +SUPPORTED_COMFYUI_CLASSTYPE_OUTPUT = ['PreviewImage', 'SaveImage', 'SaveAnimatedWEBP', 'SaveAnimatedPNG', 'VHS_VideoCombine'] def format_input_node_info(param): # format {id}.{class_type}.{alias}.{param_name} @@ -32,13 +34,14 @@ def process_workflow_meta(image_upload): logger.info(f"process_workflow_meta, {image_upload}") img = Image.open(image_upload) tran_img = ImageOps.exif_transpose(img) + logger.debug(f"process_workflow_meta, {tran_img.info.get('workflow')} {tran_img.info.get('prompt')}") return tran_img.info except Exception as e: logger.error(f"process_workflow_meta error, {e}") return None -def parse_prompt(prompt_info): +def parse_prompt(prompt_info, object_info_meta): # parse prompt to inputs and outputs try: prompt = json.loads(prompt_info) @@ -64,10 +67,10 @@ def parse_prompt(prompt_info): params_inputs.update({option_key: option_value}) node_inputs.append(param_value) - is_output = get_comfyui_object_info()[class_type]['output_node'] + is_output = object_info_meta[class_type]['output_node'] if is_output: # TODO: support multi output - if class_type == 'SaveImage': + if class_type in SUPPORTED_COMFYUI_CLASSTYPE_OUTPUT: option_key = f"{node_id}{NODE_SEP}{class_type}" if len(node_inputs) == 0: option_value = f"{node_id}{NODE_SEP}{class_type}{NODE_SEP}None" @@ -77,32 +80,37 @@ def parse_prompt(prompt_info): else: logger.warning(f"Only support SaveImage as output node, {class_type}") - return params_inputs, params_outputs + return (params_inputs, params_outputs) except Exception as e: - logger.error(f"parse_prompt error, {e}") - return None, None + st.error(f"parse_prompt error, {e} refer to {FAQ_URL}") + return (None, None) def process_image_change(): + comfyui_object_info = st.session_state.get('comfyui_object_info') upload_image = st.session_state['create_upload_image'] if upload_image: metas = process_workflow_meta(upload_image) if metas and 'prompt' in metas.keys() and 'workflow' in metas.keys(): st.session_state['create_prompt'] = metas.get('prompt') st.session_state['create_workflow'] = metas.get('workflow') - inputs, outputs = parse_prompt(metas.get('prompt')) - if inputs and outputs: + inputs, outputs = parse_prompt(metas.get('prompt'), comfyui_object_info) + if inputs: logger.info(f"create_prompt_inputs, {inputs}") + st.success(f"parse inputs from workflow image, input nodes {len(inputs)}") st.session_state['create_prompt_inputs'] = inputs + else: + st.error(f"parse workflow from image error, inputs is None, refer to {FAQ_URL}") + if outputs: logger.info(f"create_prompt_outputs, {outputs}") - st.session_state['create_prompt_outputs'] = outputs - - st.success("parse workflow from image successfully") + st.success(f"parse outputs from workflow image, output nodes {len(outputs)}") + st.session_state['create_prompt_outputs'] = outputs else: - st.error("parse workflow from image error, inputs or outputs is None") + st.error(f"parse workflow from image error, outputs is None, refer to {FAQ_URL}") + else: - st.error("the image don't contain workflow info") + st.error(f"the image don't contain workflow info, refer to {FAQ_URL}") else: st.session_state['create_prompt'] = None st.session_state['create_workflow'] = None @@ -110,21 +118,25 @@ def process_image_change(): st.session_state['create_prompt_outputs'] = {} def process_image_edit(api_prompt): + comfyui_object_info = st.session_state.get('comfyui_object_info') if api_prompt: st.session_state['create_prompt'] =api_prompt - inputs, outputs = parse_prompt(api_prompt) - if inputs and outputs: + inputs, outputs = parse_prompt(api_prompt, comfyui_object_info) + if inputs: logger.info(f"create_prompt_inputs, {inputs}") + st.success(f"parse inputs from workflow image, input nodes {len(inputs)}") st.session_state['create_prompt_inputs'] = inputs + else: + st.error(f"parse workflow from image error, inputs is None, refer to {FAQ_URL}") + if outputs: logger.info(f"create_prompt_outputs, {outputs}") - st.session_state['create_prompt_outputs'] = outputs - - st.success("parse workflow from image successfully") + st.success(f"parse outputs from workflow image, output nodes {len(outputs)}") + st.session_state['create_prompt_outputs'] = outputs else: - st.error("parse workflow from image error, inputs or outputs is None") + st.error(f"parse workflow from image error, outputs is None, refer to {FAQ_URL}") else: - st.error("the image don't contain workflow info") + st.error(f"the image don't contain workflow info, refer to {FAQ_URL}") def get_node_input_config(input_param, app_input_name, app_input_description): @@ -132,7 +144,7 @@ def get_node_input_config(input_param, app_input_name, app_input_description): option_params_value = params_inputs[input_param] logger.debug(f"get_node_input_config, {input_param} {option_params_value}") node_id, class_type, param, param_value = option_params_value.split(NODE_SEP) - comfyui_object_info = get_comfyui_object_info() + comfyui_object_info = st.session_state.get('comfyui_object_info') class_meta = comfyui_object_info[class_type] class_input = class_meta['input']['required'] if 'optional' in class_meta['input'].keys(): @@ -327,6 +339,12 @@ def edit_app_ui(app): header_row.title("🌱 Edit app from comfyui workflow") header_row.button("Back Workspace", help="Back to your workspace", key="edit_back_workspace", on_click=on_edit_workspace) + try: + comfyui_object_info = get_comfyui_object_info() + st.session_state['comfyui_object_info'] = comfyui_object_info + except Exception as e: + st.error(f"connect to comfyui node error, {e}") + st.stop() # upload workflow image and config params with st.expander("### :one: Upload image of comfyui workflow", expanded=True): @@ -386,19 +404,19 @@ def edit_app_ui(app): input_param = input_params[0] add_input_config_param(params_inputs_options, 1, input_param) else: - add_input_config_param(params_inputs_options, 1) + add_input_config_param(params_inputs_options, 1, None) if len(input_params) > 1: input_param_2 = input_params[1] add_input_config_param(params_inputs_options, 2, input_param_2) else: - add_input_config_param(params_inputs_options, 2) + add_input_config_param(params_inputs_options, 2, None) if len(input_params) > 2: input_param_3 = input_params[2] add_input_config_param(params_inputs_options, 3, input_param_3) else: - add_input_config_param(params_inputs_options, 3) + add_input_config_param(params_inputs_options, 3, None) with st.container(): @@ -424,7 +442,7 @@ def edit_app_ui(app): output_param_1 = output_params[0] add_output_config_param(params_outputs_options, 1, output_param_1) else: - add_output_config_param(params_outputs_options, 1) + add_output_config_param(params_outputs_options, 1, None) with st.container(): operation_row = row([0.15, 0.7, 0.15]) @@ -437,7 +455,7 @@ def edit_app_ui(app): st.success("Save app successfully, back your workspace") st.stop() else: - st.error("Save app error, please check up app params") + st.error(f"Save app error, please check up app params, refer to {FAQ_URL}") operation_row.empty() next_placeholder = operation_row.empty() @@ -446,7 +464,7 @@ def on_new_workspace(): st.session_state.pop('new_app', None) logger.info("back to workspace") -def add_input_config_param(params_inputs_options, index, input_param=None): +def add_input_config_param(params_inputs_options, index, input_param): if not input_param: input_param = { 'name': None, @@ -464,7 +482,7 @@ def add_input_config_param(params_inputs_options, index, input_param=None): param_input_row.text_input("App Input Description", value=input_param['help'], placeholder="Param Description", key=f"input_param{index}_desc", help="Input param description") -def add_output_config_param(params_outputs_options, index, output_param=None): +def add_output_config_param(params_outputs_options, index, output_param): if not output_param: output_param = { 'name': None, @@ -494,13 +512,20 @@ def new_app_ui(): st.warning("Please go to homepage for your login :point_left:") st.stop() + try: + comfyui_object_info = get_comfyui_object_info() + st.session_state['comfyui_object_info'] = comfyui_object_info + except Exception as e: + st.error(f"connect to comfyui node error, {e}") + st.stop() + # upload workflow image and config params with st.expander("### :one: Upload image of comfyui workflow", expanded=True): image_col1, image_col2 = st.columns([0.5, 0.5]) with image_col1: - st.file_uploader("Upload image *", type=["png", "jpg", "jpeg"], + st.file_uploader("Upload image from comfyui outputs *", type=["png", "jpg", "jpeg", "webp"], key="create_upload_image", - help="upload image of comfyui workflow") + help="upload image from comfyui output folder", accept_multiple_files=False) process_image_change() with image_col2: @@ -530,15 +555,15 @@ def new_app_ui(): params_inputs = st.session_state.get('create_prompt_inputs', {}) params_inputs_options = list(params_inputs.keys()) - add_input_config_param(params_inputs_options, 1) - add_input_config_param(params_inputs_options, 2) - add_input_config_param(params_inputs_options, 3) + add_input_config_param(params_inputs_options, 1, None) + add_input_config_param(params_inputs_options, 2, None) + add_input_config_param(params_inputs_options, 3, None) with st.container(): st.markdown("Output Params:") params_outputs = st.session_state.get('create_prompt_outputs', {}) params_outputs_options = list(params_outputs.keys()) - add_output_config_param(params_outputs_options, 1) + add_output_config_param(params_outputs_options, 1, None) with st.container(): @@ -554,7 +579,7 @@ def new_app_ui(): elif submit_info == 'exist': st.error("Submit app error, app name has existed") else: - st.error("Submit app error, please check up app params") + st.error(f"Submit app error, please check up app params, refer to {FAQ_URL}") operation_row.empty() diff --git a/modules/preview_app.py b/modules/preview_app.py index 093a34a..679a83c 100644 --- a/modules/preview_app.py +++ b/modules/preview_app.py @@ -3,7 +3,7 @@ import streamlit as st import modules.page as page from streamlit_extras.row import row -from modules import get_comfy_client, get_inner_comfy_client, get_workspace_model +from modules import get_comfy_client, get_workspace_model from modules.workspace_model import AppStatus def on_preview_workspace():