Spaces:
Sleeping
Sleeping
| import json | |
| import os | |
| import time | |
| from inspect import signature | |
| import huggingface_hub as hub | |
| import gradio as gr | |
| import gradio.utils | |
| from gradio.sketch.sketchbox import SketchBox | |
| from gradio.sketch.utils import ai, get_header, set_kwarg | |
| def create(app_file: str, config_file: str): | |
| file_name = os.path.basename(app_file) | |
| folder_name = os.path.basename(os.path.dirname(app_file)) | |
| created_fns_namespace = {} | |
| nonconfigurable_params = ["every", "inputs", "render", "key"] | |
| default_kwargs_map = { | |
| gr.Image: {"type": "filepath"}, | |
| gr.Audio: {"type": "filepath"}, | |
| gr.Chatbot: {"type": "messages"}, | |
| } | |
| quick_component_list = [ | |
| gr.Textbox, | |
| gr.Number, | |
| gr.Button, | |
| gr.Markdown, | |
| gr.State, | |
| ] | |
| all_component_list = [ | |
| gr.AnnotatedImage, | |
| # gr.Accordion, | |
| gr.Audio, | |
| gr.BarPlot, | |
| gr.BrowserState, | |
| gr.Button, | |
| gr.Chatbot, | |
| gr.Checkbox, | |
| gr.CheckboxGroup, | |
| gr.Code, | |
| gr.ColorPicker, | |
| gr.Dataframe, | |
| gr.DateTime, | |
| gr.Dropdown, | |
| gr.File, | |
| gr.Gallery, | |
| gr.HighlightedText, | |
| gr.HTML, | |
| gr.Image, | |
| gr.ImageEditor, | |
| gr.JSON, | |
| gr.Label, | |
| gr.LinePlot, | |
| gr.Markdown, | |
| gr.Model3D, | |
| gr.MultimodalTextbox, | |
| gr.Number, | |
| gr.Radio, | |
| gr.Slider, | |
| gr.State, | |
| gr.Textbox, | |
| gr.Timer, | |
| gr.Video, | |
| ] | |
| def get_component_by_name(name): | |
| return [ | |
| component for component in all_component_list if component.__name__ == name | |
| ][0] | |
| def get_box(_slot, i, gp=None): | |
| parent = _slot | |
| target = _slot[i[0]] if isinstance(_slot, list) and i[0] < len(_slot) else None | |
| if len(i) > 1: | |
| gp, parent, target = get_box(target, i[1:], parent) | |
| return gp, parent, target | |
| def add_component( | |
| component, layout, components, dependencies, add_index, new_component_id | |
| ): | |
| gp, parent, _ = get_box(layout, add_index) | |
| if isinstance(parent, int): | |
| parent = [parent] | |
| if gp: | |
| gp[add_index[-2]] = parent | |
| parent.insert(add_index[-1], new_component_id) | |
| default_kwargs = default_kwargs_map.get(component, {}).copy() | |
| components[new_component_id] = [component.__name__, default_kwargs, ""] | |
| component_name = component.__name__.lower() | |
| existing_names = [components[i][2] for i in components] | |
| var_name = component_name | |
| i = 2 | |
| while var_name in existing_names: | |
| var_name = component_name + "_" + str(i) | |
| i += 1 | |
| components[new_component_id][2] = var_name | |
| return ( | |
| layout, | |
| components, | |
| dependencies, | |
| "modify_component", | |
| new_component_id, | |
| new_component_id + 1, | |
| gr.Button(interactive=True), | |
| ) | |
| def set_hf_token(token): | |
| try: | |
| hub.login(token) | |
| except BaseException as err: | |
| raise gr.Error("Invalid Hugging Face token.") from err | |
| gr.Success("Token set successfully.", duration=2) | |
| return token | |
| with gr.Blocks() as demo: | |
| _id = gr.State(0) | |
| # Below was giving issues, commenting out for now | |
| # if os.path.exists(config_file): | |
| # with open(config_file) as f: | |
| # config = json.load(f) | |
| # _layout = config["layout"] | |
| # _components = {int(k): v for k, v in config["components"].items()} | |
| # _new_component_id = len(_components) | |
| # mode = gr.State("default") | |
| # else: | |
| _layout = [] | |
| _components = {} | |
| _dependencies = [] | |
| _new_component_id = 0 | |
| mode = gr.State("add_component") | |
| new_component_id = gr.State(_new_component_id) | |
| components = gr.State(_components) | |
| dependencies = gr.State(_dependencies) | |
| layout = gr.State(_layout) | |
| add_index = gr.State([_new_component_id]) | |
| modify_id = gr.State(None) | |
| saved = gr.State(False) | |
| hf_token = gr.State(hub.get_token() or os.getenv("HF_TOKEN")) | |
| add_fn_btn = gr.Button( | |
| "+ Add Function", scale=0, interactive=False, render=False | |
| ) | |
| with gr.Sidebar() as left_sidebar: | |
| def render_sidebar( | |
| _mode, | |
| _add_index, | |
| _new_component_id, | |
| _components, | |
| _dependencies, | |
| _modify_id, | |
| _hf_token, | |
| ): | |
| if _mode == "default" and len(_components) == 0: | |
| _mode = "add_component" | |
| _add_index = [0] | |
| if _mode == "default": | |
| gr.Markdown("## Placement") | |
| gr.Markdown("Click on a '+' button to add a component.") | |
| if _mode == "add_component": | |
| gr.Markdown("## Selection") | |
| if len(_components) == 0: | |
| gr.Markdown("Select first component to place.") | |
| else: | |
| gr.Markdown("Select component to place in selected area.") | |
| for component in quick_component_list: | |
| gr.Button(component.__name__, size="md").click( | |
| lambda _layout, _component=component: add_component( | |
| _component, | |
| _layout, | |
| _components, | |
| _dependencies, | |
| _add_index, | |
| _new_component_id, | |
| ), | |
| layout, | |
| [ | |
| layout, | |
| components, | |
| dependencies, | |
| mode, | |
| modify_id, | |
| new_component_id, | |
| add_fn_btn, | |
| ], | |
| ) | |
| any_component_search = gr.Dropdown( | |
| [component.__name__ for component in all_component_list], | |
| container=True, | |
| label="Other Components...", | |
| interactive=True, | |
| ) | |
| any_component_search.change( | |
| lambda _component, _layout: add_component( | |
| get_component_by_name(_component), | |
| _layout, | |
| _components, | |
| _dependencies, | |
| _add_index, | |
| _new_component_id, | |
| ), | |
| [any_component_search, layout], | |
| [ | |
| layout, | |
| components, | |
| dependencies, | |
| mode, | |
| modify_id, | |
| new_component_id, | |
| add_fn_btn, | |
| ], | |
| ) | |
| if _mode == "modify_component": | |
| component_name, kwargs, var_name = _components[_modify_id] | |
| gr.Markdown( | |
| "## Configuration\nHover over a component to add new components when done configuring." | |
| ) | |
| var_name_box = gr.Textbox(var_name, label="Variable Name") | |
| def set_var_name(name): | |
| _components[_modify_id][2] = name | |
| return _components | |
| gr.on( | |
| [var_name_box.blur, var_name_box.submit], | |
| set_var_name, | |
| var_name_box, | |
| components, | |
| ) | |
| gr.Markdown( | |
| 'Set args below with python syntax, e.g. `True`, `5`, or `["choice1", "choice2"]`.' | |
| ) | |
| component = get_component_by_name(component_name) | |
| arguments = list(signature(component.__init__).parameters.keys())[ | |
| 1: | |
| ] | |
| for arg in arguments: | |
| if arg in nonconfigurable_params: | |
| continue | |
| arg_value = kwargs.get(arg, "") | |
| arg_box = gr.Textbox( | |
| arg_value, | |
| label=arg, | |
| info=f"<a href='https://www.gradio.app/docs/gradio/{component_name.lower()}#param-{component_name.lower()}-{arg.lower().replace('_', '-')}' target='_blank'>docs</a>", | |
| ) | |
| def set_arg(value, arg=arg): | |
| set_kwarg(_components[_modify_id][1], arg, value) | |
| return _components | |
| gr.on( | |
| [arg_box.blur, arg_box.submit], set_arg, arg_box, components | |
| ) | |
| if _mode == "modify_function": | |
| dep = _dependencies[_modify_id] | |
| _triggers, _inputs, _outputs, var_name, _history, _code = dep | |
| gr.Markdown("## Event Listeners") | |
| function_name_box = gr.Textbox(var_name, label="Function Name") | |
| def set_fn_name(name): | |
| dep[3] = name | |
| return _dependencies | |
| gr.on( | |
| [function_name_box.blur, function_name_box.submit], | |
| set_fn_name, | |
| function_name_box, | |
| dependencies, | |
| ) | |
| gr.Markdown( | |
| "Mark the components in the diagram as inputs or outputs, and select their triggers. Then use the code generator below." | |
| ) | |
| if not _hf_token: | |
| input_hf_token = gr.Textbox( | |
| label="HF Token", | |
| info="Needed for code generation. Copy from [HF Token Page](https://huggingface.co/settings/token). Token requires access to inference providers.", | |
| type="password", | |
| ) | |
| submit_token_btn = gr.Button("Submit Token", size="md") | |
| submit_token_btn.click(set_hf_token, input_hf_token, hf_token) | |
| else: | |
| new_prompt_placeholder = "Describe what the function should do." | |
| edit_prompt_placeholder = "Describe how to change the code generation. Click 'Reset Code' to start over." | |
| history_exists = len(_history) > 0 | |
| prompt = gr.Textbox( | |
| label="Prompt", | |
| lines=3, | |
| placeholder=edit_prompt_placeholder | |
| if history_exists | |
| else new_prompt_placeholder, | |
| interactive=True, | |
| ) | |
| no_components_are_set = ( | |
| len(_dependencies[_modify_id][1]) | |
| == 0 + len(_dependencies[_modify_id][2]) | |
| == 0 | |
| ) | |
| if no_components_are_set: | |
| gr.Markdown( | |
| "Set **all inputs and outputs** before generating code." | |
| ) | |
| new_generate_text = "Generate Code" | |
| update_generate_text = "Update Code" | |
| generate_code_btn = gr.Button( | |
| update_generate_text | |
| if history_exists | |
| else new_generate_text, | |
| size="md", | |
| interactive=not no_components_are_set, | |
| ) | |
| reset_code_btn = gr.Button( | |
| "Reset Code", size="md", visible=history_exists | |
| ) | |
| __inputs = [_components[c][2] for c in _inputs] | |
| __outputs = [_components[c][2] for c in _outputs] | |
| _code = ( | |
| _code | |
| if _code is not None | |
| else f"""{get_header(var_name, __inputs)} | |
| ... | |
| return {", ".join(["..." for _ in __outputs])}""" | |
| ) | |
| fn_code = gr.Code(_code, lines=4, language="python") | |
| save_code_btn = gr.Button("Save Code", size="md") | |
| history = gr.JSON(_history, visible=False) | |
| def generate(_prompt, _history): | |
| yield from ai( | |
| _history + [[_prompt, None]], | |
| _hf_token, | |
| var_name, | |
| [ | |
| ( | |
| _components[c][2], | |
| get_component_by_name(_components[c][0]), | |
| _components[c][1], | |
| ) | |
| for c in _inputs | |
| ], | |
| [ | |
| ( | |
| get_component_by_name(_components[c][0]), | |
| _components[c][1], | |
| ) | |
| for c in _outputs | |
| ], | |
| ) | |
| def append_to_history( | |
| history: list[tuple[str, str]], prompt: str, code: str | |
| ): | |
| history.append((prompt, code)) | |
| return ( | |
| history, | |
| gr.Button(visible=True), | |
| gr.Textbox( | |
| value="", placeholder=edit_prompt_placeholder | |
| ), | |
| gr.Button(update_generate_text), | |
| ) | |
| generate_code_btn.click( | |
| generate, [prompt, history], fn_code | |
| ).then( | |
| append_to_history, | |
| [history, prompt, fn_code], | |
| [history, reset_code_btn, prompt, generate_code_btn], | |
| show_progress="hidden", | |
| ) | |
| def reset_code(_dependencies, _modify_id): | |
| _dependencies[_modify_id][4] = [] | |
| _dependencies[_modify_id][5] = None | |
| return ( | |
| get_header(var_name, __inputs), | |
| gr.Button(visible=False), | |
| gr.Textbox(placeholder=new_prompt_placeholder), | |
| gr.Button(new_generate_text), | |
| [], | |
| _dependencies, | |
| ) | |
| reset_code_btn.click( | |
| reset_code, | |
| [dependencies, modify_id], | |
| [ | |
| fn_code, | |
| reset_code_btn, | |
| prompt, | |
| generate_code_btn, | |
| history, | |
| dependencies, | |
| ], | |
| ) | |
| def save_code(_history, _code): | |
| try: | |
| exec(_code, created_fns_namespace) | |
| except BaseException as e: | |
| raise gr.Error(f"Error saving function: {e}") from e | |
| if var_name not in created_fns_namespace: | |
| raise gr.Error( | |
| f"Function '{var_name}' not found in code." | |
| ) | |
| dep[4] = ( | |
| [] | |
| if len(_history) == 0 | |
| else _history[:-1] + [[_history[-1][0], _code]] | |
| ) | |
| dep[5] = _code | |
| gr.Success("Function saved.", duration=2) | |
| return _dependencies | |
| save_code_btn.click(save_code, [history, fn_code], dependencies) | |
| done_function_btn = gr.Button("Done", variant="primary", size="md") | |
| done_function_btn.click( | |
| lambda: ["default", None], None, [mode, modify_id] | |
| ) | |
| del_function_btn = gr.Button( | |
| "Delete Function", variant="stop", size="md" | |
| ) | |
| def del_function(): | |
| del _dependencies[_modify_id] | |
| return _dependencies, "default", None | |
| del_function_btn.click( | |
| del_function, None, [dependencies, mode, modify_id] | |
| ) | |
| with gr.Row(): | |
| gr.Markdown("## Sketching '" + folder_name + "/" + file_name + "'") | |
| add_fn_btn.render() | |
| save_btn = gr.Button("Save & Render", variant="primary", scale=0) | |
| deploy_to_spaces_btn = gr.Button( | |
| "Deploy to Spaces", | |
| visible=False, | |
| scale=0, | |
| min_width=240, | |
| icon=gradio.utils.get_icon_path("huggingface-logo.svg"), | |
| ) | |
| def app(_layout, _components, _dependencies, saved, _modify_id, _mode): | |
| boxes = [] | |
| rendered_components = {} | |
| function_mode = _mode == "modify_function" | |
| def render_slot(slot, is_column, index, depth=1): | |
| container = gr.Column() if is_column else gr.Row() | |
| with container: | |
| for i, element in enumerate(slot): | |
| this_index = index + [i] | |
| if isinstance(element, list): | |
| if saved: | |
| render_slot( | |
| element, not is_column, this_index, depth + 1 | |
| ) | |
| else: | |
| with SketchBox( | |
| is_container=True, function_mode=function_mode | |
| ) as box: | |
| render_slot( | |
| element, not is_column, this_index, depth + 1 | |
| ) | |
| boxes.append((box, this_index)) | |
| continue | |
| component_name, kwargs, var_name = _components[element] | |
| component = get_component_by_name(component_name) | |
| if saved: | |
| rendered_components[element] = component(**kwargs) | |
| else: | |
| if function_mode: | |
| triggers = [ | |
| t | |
| for c, t in _dependencies[_modify_id][0] | |
| if c == element | |
| ] | |
| is_input = element in _dependencies[_modify_id][1] | |
| is_output = element in _dependencies[_modify_id][2] | |
| else: | |
| triggers = None | |
| is_input = False | |
| is_output = False | |
| with SketchBox( | |
| component_type=component.__name__.lower(), | |
| var_name=var_name, | |
| active=_modify_id == element and not function_mode, | |
| function_mode=function_mode, | |
| event_list=component.EVENTS | |
| if hasattr(component, "EVENTS") | |
| else None, | |
| is_input=is_input, | |
| is_output=is_output, | |
| triggers=triggers, | |
| ) as box: | |
| component(**kwargs) | |
| boxes.append((box, this_index)) | |
| render_slot(_layout, True, []) | |
| for box, index in boxes: | |
| def box_action( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| _modify_id, | |
| data: gr.SelectData, | |
| index=index, | |
| ): | |
| if data.value in ("up", "down", "left", "right"): | |
| if len(index) % 2 == 1: # vertical | |
| if data.value == "down": | |
| index[-1] += 1 | |
| elif data.value == "left": | |
| index.append(0) | |
| elif data.value == "right": | |
| index.append(1) | |
| elif data.value == "right": | |
| index[-1] += 1 | |
| elif data.value == "up": | |
| index.append(0) | |
| elif data.value == "down": | |
| index.append(1) | |
| return ( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| "add_component", | |
| index, | |
| None, | |
| ) | |
| if data.value == "delete": | |
| def delete_index(_layout, index): | |
| gp, parent, target = get_box(_layout, index) | |
| parent.remove(target) | |
| if isinstance(target, int): | |
| del _components[target] | |
| if len(parent) == 0 and len(index) > 1: | |
| delete_index(_layout, index[:-1]) | |
| elif len(parent) == 1 and gp: | |
| gp[index[-2]] = parent[0] | |
| delete_index(_layout, index) | |
| if len(_layout) == 0: | |
| return ( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| "add_component", | |
| [0], | |
| None, | |
| ) | |
| else: | |
| return ( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| "default", | |
| [], | |
| None, | |
| ) | |
| if data.value == "modify": | |
| *_, target = get_box(_layout, index) | |
| return ( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| "modify_component", | |
| None, | |
| target, | |
| ) | |
| if data.value in ["input", "output"]: | |
| *_, target = get_box(_layout, index) | |
| component_list = _dependencies[_modify_id][ | |
| 1 if data.value == "input" else 2 | |
| ] | |
| if target in component_list: | |
| component_list.remove(target) | |
| else: | |
| component_list.append(target) | |
| return ( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| "modify_function", | |
| None, | |
| _modify_id, | |
| ) | |
| if data.value.startswith("on:"): | |
| *_, target = get_box(_layout, index) | |
| event = data.value[3:] | |
| triggers = _dependencies[_modify_id][0] | |
| if (target, event) in triggers: | |
| triggers.remove((target, event)) | |
| else: | |
| triggers.append((target, event)) | |
| return ( | |
| _layout, | |
| _components, | |
| _dependencies, | |
| "modify_function", | |
| None, | |
| _modify_id, | |
| ) | |
| box.select( | |
| box_action, | |
| [layout, components, dependencies, modify_id], | |
| [layout, components, dependencies, mode, add_index, modify_id], | |
| ) | |
| if saved: | |
| for triggers, inputs, outputs, fn_name, *_, code in _dependencies: | |
| rendered_triggers = [ | |
| getattr(rendered_components[c], t) for c, t in triggers | |
| ] | |
| rendered_inputs = [rendered_components[c] for c in inputs] | |
| rendered_outputs = [rendered_components[c] for c in outputs] | |
| if code: | |
| try: | |
| gr.on( | |
| rendered_triggers, | |
| created_fns_namespace[fn_name], | |
| rendered_inputs, | |
| rendered_outputs, | |
| ) | |
| except Exception: | |
| pass | |
| else: | |
| output_count = len(rendered_outputs) | |
| fn_output = ( | |
| [gr.skip()] * output_count | |
| if output_count > 1 | |
| else gr.skip() | |
| if output_count == 1 | |
| else None | |
| ) | |
| def sleep(*_): | |
| print("sleeping") | |
| time.sleep(1) | |
| return fn_output | |
| gr.on( | |
| rendered_triggers, sleep, rendered_inputs, rendered_outputs | |
| ) | |
| with gr.Sidebar(position="right", open=False) as right_sidebar: | |
| gr.Markdown("## Functions") | |
| def render_deps(_dependencies): | |
| for i, dep in enumerate(_dependencies): | |
| fn_btn = gr.Button(dep[3], size="md") | |
| def load_fn(i=i): | |
| return "modify_function", i | |
| fn_btn.click(load_fn, outputs=[mode, modify_id]) | |
| def add_fn(_dependencies): | |
| _dependencies.append( | |
| [[], [], [], f"fn_{len(_dependencies) + 1}", [], None] | |
| ) | |
| return ( | |
| _dependencies, | |
| "modify_function", | |
| len(_dependencies) - 1, | |
| gr.Sidebar(open=True), | |
| ) | |
| add_fn_btn.click( | |
| add_fn, dependencies, [dependencies, mode, modify_id, right_sidebar] | |
| ) | |
| gr.Markdown("## Generated File") | |
| code = gr.Code(language="python", interactive=False, show_label=False) | |
| def render_code(_layout, _components, _dependencies): | |
| code_str = "" | |
| def render_code_slot(slot, is_column, index, depth=1): | |
| nonlocal code_str | |
| for i, element in enumerate(slot): | |
| this_index = index + [i] | |
| if isinstance(element, list): | |
| code_str += ( | |
| " " * depth | |
| + "with gr." | |
| + ("Row" if is_column else "Column") | |
| + "():\n" | |
| ) | |
| render_code_slot( | |
| element, not is_column, this_index, depth + 1 | |
| ) | |
| continue | |
| component_name, kwargs, var_name = _components[element] | |
| code_str += ( | |
| " " * depth + var_name + " = gr." + component_name + "(" | |
| ) | |
| for i, (k, v) in enumerate(kwargs.items()): | |
| v = ( | |
| f'"{v}"'.replace("\n", "\\n") | |
| if isinstance(v, str) | |
| else v | |
| ) | |
| if i != 0: | |
| code_str += ", " | |
| code_str += f"{k}={v}" | |
| code_str += ")\n" | |
| render_code_slot(_layout, True, []) | |
| for dep in _dependencies: | |
| triggers = [_components[c][2] + "." + t for c, t in dep[0]] | |
| inputs = [_components[c][2] for c in dep[1]] | |
| outputs = [_components[c][2] for c in dep[2]] | |
| fn_name = dep[3] | |
| if dep[5] is not None: | |
| fn_code = dep[5].replace("\n", "\n ") | |
| else: | |
| fn_code = f"""def {fn_name}({", ".join(inputs)}): | |
| ... | |
| return {", ".join(["..." for _ in outputs])}""" | |
| code_str += f""" | |
| @{triggers[0] + "(" if len(triggers) == 1 else "gr.on([" + ", ".join(triggers) + "], "}inputs=[{", ".join(inputs)}], outputs=[{", ".join(outputs)}]) | |
| {fn_code} | |
| """ | |
| code_str = f"""import gradio as gr | |
| with gr.Blocks() as demo: | |
| {code_str} | |
| demo.launch()""" | |
| return code_str | |
| def save(saved, code, deps): | |
| with open(app_file, "w") as f: | |
| f.write(code) | |
| with open(config_file, "w") as f: | |
| json.dump( | |
| { | |
| "layout": _layout, | |
| "components": _components, | |
| }, | |
| f, | |
| ) | |
| return [ | |
| not saved, | |
| "Save & Render" if saved else "Edit Sketch", | |
| gr.Button(visible=saved), | |
| gr.Button(visible=not saved), | |
| "default", | |
| gr.Sidebar(open=saved), | |
| gr.Sidebar(open=saved and len(deps) > 0), | |
| ] | |
| deploy_to_spaces_btn.click( | |
| fn=None, | |
| inputs=code, | |
| js="""(code) => { | |
| code = encodeURIComponent(code); | |
| url = `https://huggingface.co/new-space?name=new-space&sdk=gradio&files[0][path]=app.py&files[0][content]=${code}` | |
| window.open(url, '_blank') | |
| }""", | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create("app.py", "app.json") | |
| demo.launch() |