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)