gradio2toga / app.py
broadfield-dev's picture
Update app.py
2b4ec71 verified
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)