Spaces:
Running
Running
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() | |