Spaces:
Sleeping
Sleeping
import gradio as gr | |
import ast | |
import astor | |
import os | |
from io import StringIO | |
# Mapping of Gradio components to Toga equivalents | |
GRADIO_TO_TOGA = { | |
"Textbox": ("toga.TextInput", "style=Pack(padding=5)", "Text Input"), | |
"Image": ("toga.Label", "Placeholder: Image file picker", "Image Upload"), | |
"Button": ("toga.Button", "on_press=button_handler", "Submit"), | |
"Markdown": ("toga.Label", "style=Pack(padding=5, font_size=14)", "Markdown Text"), | |
"Audio": ("toga.Label", "Placeholder: Audio file picker", "Audio Upload"), | |
"Video": ("toga.Label", "Placeholder: Video file picker", "Video Upload"), | |
} | |
# Parse Gradio app code | |
def parse_gradio_code(code): | |
try: | |
tree = ast.parse(code) | |
components = [] | |
functions = [] | |
imports = [] | |
for node in ast.walk(tree): | |
# Extract function definitions | |
if isinstance(node, ast.FunctionDef): | |
functions.append(node) | |
# Extract Gradio component calls | |
if isinstance(node, ast.Call) and hasattr(node.func, 'attr'): | |
if node.func.attr in GRADIO_TO_TOGA: | |
component = node.func.attr | |
args = [ast.dump(arg, annotate_fields=False) for arg in node.args] | |
kwargs = {kw.arg: ast.dump(kw.value, annotate_fields=False) for kw in node.keywords} | |
components.append({"type": component, "args": args, "kwargs": kwargs}) | |
# Extract imports | |
if isinstance(node, (ast.Import, ast.ImportFrom)): | |
imports.append(node) | |
return components, functions, imports | |
except Exception as e: | |
return [], [], [f"Error parsing code: {str(e)}"] | |
# Generate Toga code | |
def generate_toga_code(components, functions, imports): | |
toga_code = [ | |
"import toga", | |
"from toga.style import Pack", | |
"from toga.style.pack import COLUMN", | |
"" | |
] | |
# Add imports | |
toga_code.append("# Dependencies from Gradio app") | |
for imp in imports: | |
toga_code.append(astor.to_source(imp).strip()) | |
toga_code.append("") | |
# Add function definitions | |
toga_code.append("# Functions from Gradio app") | |
for func in functions: | |
toga_code.append(astor.to_source(func).strip()) | |
toga_code.append("") | |
# Generate UI | |
toga_code.extend([ | |
"def build(app):", | |
" box = toga.Box(style=Pack(direction=COLUMN, padding=10))", | |
"" | |
]) | |
for idx, comp in enumerate(components): | |
toga_comp, extra, label_prefix = GRADIO_TO_TOGA.get(comp["type"], ("toga.Label", f"Placeholder: {comp['type']} not supported", "Unknown")) | |
if comp["type"] == "Textbox": | |
label = comp["kwargs"].get("label", f"'{label_prefix} {idx}'") | |
toga_code.append(f" label_{idx} = toga.Label({label}, style=Pack(padding=(5, 5, 0, 5)))") | |
toga_code.append(f" input_{idx} = {toga_comp}({extra})") | |
toga_code.append(f" box.add(label_{idx})") | |
toga_code.append(f" box.add(input_{idx})") | |
elif comp["type"] in ["Image", "Audio", "Video"]: | |
file_types = { | |
"Image": "['png', 'jpg', 'jpeg']", | |
"Audio": "['mp3', 'wav']", | |
"Video": "['mp4', 'avi']" | |
}.get(comp["type"], "['*']") | |
toga_code.append(f" {comp['type'].lower()}_label_{idx} = toga.Label('{label_prefix}: None selected', style=Pack(padding=5))") | |
toga_code.append(f" def select_{comp['type'].lower()}_{idx}(widget):") | |
toga_code.append(f" path = app.main_window.open_file_dialog('Select {comp['type']}', file_types={file_types})") | |
toga_code.append(f" if path:") | |
toga_code.append(f" {comp['type'].lower()}_label_{idx}.text = f'{label_prefix}: {{path}}'") | |
toga_code.append(f" {comp['type'].lower()}_button_{idx} = toga.Button('Upload {comp['type']}', on_press=select_{comp['type'].lower()}_{idx}, style=Pack(padding=5))") | |
toga_code.append(f" box.add(toga.Label('{label_prefix}', style=Pack(padding=(5, 5, 0, 5))))") | |
toga_code.append(f" box.add({comp['type'].lower()}_button_{idx})") | |
toga_code.append(f" box.add({comp['type'].lower()}_label_{idx})") | |
elif comp["type"] == "Button": | |
label = comp["kwargs"].get("value", f"'{label_prefix} {idx}'") | |
# Check for associated function in click events | |
click_fn = comp["kwargs"].get("fn", f"{functions[0].name if functions else 'function'}") | |
toga_code.append(f" def button_handler_{idx}(widget):") | |
toga_code.append(f" # Call {click_fn} with inputs") | |
toga_code.append(f" pass # Implement logic using input values") | |
toga_code.append(f" button_{idx} = {toga_comp}(label={label}, {extra})") | |
toga_code.append(f" box.add(button_{idx})") | |
elif comp["type"] == "Markdown": | |
text = comp["args"][0] if comp["args"] else f"'{label_prefix} {idx}'" | |
toga_code.append(f" markdown_{idx} = {toga_comp}({text}, {extra})") | |
toga_code.append(f" box.add(markdown_{idx})") | |
else: | |
toga_code.append(f" placeholder_{idx} = {toga_comp}('{extra}', style=Pack(padding=5))") | |
toga_code.append(f" box.add(placeholder_{idx})") | |
toga_code.extend([ | |
"", | |
" return box", | |
"", | |
"def main():", | |
" return toga.App('Gradio to Toga App', 'org.example.gradio2toga', startup=build)", | |
"", | |
"if __name__ == '__main__':", | |
" main().main_loop()" | |
]) | |
return "\n".join(toga_code) | |
# Conversion function | |
def convert_gradio_to_toga(file, code_text): | |
if not file and not code_text: | |
return None, None, "Please upload a Gradio app Python file or paste code in the textbox." | |
try: | |
# Use file if provided, else use text | |
code = file.decode('utf-8') if file else code_text | |
components, functions, imports = parse_gradio_code(code) | |
if not components and functions and isinstance(functions[0], str): | |
return None, None, f"Error: {functions[0]}" | |
toga_code = generate_toga_code(components, functions, imports) | |
output_path = "/tmp/toga_app.py" | |
with open(output_path, "w") as f: | |
f.write(toga_code) | |
return output_path, toga_code, "Conversion successful! Preview the Toga code below and download the app." | |
except Exception as e: | |
return None, None, f"Error: {str(e)}" | |
# Gradio interface | |
with gr.Blocks(theme=gr.themes.Soft()) as interface: | |
gr.Markdown("# Gradio to Toga Converter") | |
gr.Markdown("Upload a Gradio app (.py) or paste the code below to convert to a Toga desktop app.") | |
file_input = gr.File(label="Upload Gradio App", file_types=[".py"]) | |
code_input = gr.Textbox(label="Paste Gradio Code", lines=10, placeholder="Paste your Gradio Python code here...") | |
convert_button = gr.Button("Convert to Toga") | |
toga_preview = gr.Textbox(label="Toga Code Preview", lines=15, interactive=False) | |
output = gr.File(label="Download Toga App") | |
status = gr.Textbox(label="Status", interactive=False) | |
convert_button.click( | |
fn=convert_gradio_to_toga, | |
inputs=[file_input, code_input], | |
outputs=[output, toga_preview, status] | |
) | |
# Run Gradio for Hugging Face Spaces | |
if __name__ == "__main__": | |
interface.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)), quiet=True) |