File size: 6,550 Bytes
4b6f4f0
 
 
 
 
 
 
05977dd
 
 
 
 
 
4b6f4f0
 
05977dd
4b6f4f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05977dd
4b6f4f0
05977dd
4b6f4f0
 
 
 
05977dd
4b6f4f0
05977dd
4b6f4f0
 
 
 
 
 
 
 
05977dd
4b6f4f0
 
05977dd
4b6f4f0
05977dd
4b6f4f0
 
 
05977dd
 
 
 
 
 
 
 
 
4b6f4f0
05977dd
 
 
 
 
4b6f4f0
 
05977dd
4b6f4f0
05977dd
4b6f4f0
05977dd
4b6f4f0
 
 
05977dd
 
4b6f4f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05977dd
4b6f4f0
 
a364f6d
4b6f4f0
 
 
 
 
 
a364f6d
4b6f4f0
 
05977dd
4b6f4f0
 
 
a364f6d
4b6f4f0
a364f6d
4b6f4f0
 
05977dd
 
a364f6d
 
05977dd
a364f6d
 
05977dd
 
 
 
a364f6d
05977dd
4b6f4f0
a364f6d
4b6f4f0
a364f6d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import gradio as gr
import ast
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 = []
        
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                functions.append(node.name)
            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})
        
        return components, functions
    except Exception as e:
        return [], [f"Error parsing code: {str(e)}"]

# Generate Toga code
def generate_toga_code(components, functions):
    toga_code = [
        "import toga",
        "from toga.style import Pack",
        "from toga.style.pack import COLUMN",
        "",
        "# Original functions (copy these from your Gradio app)",
        f"# Detected functions: {', '.join(functions) if functions else 'None'}",
        "",
        "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}'")
            toga_code.append(f"    def button_handler_{idx}(widget):")
            toga_code.append(f"        # Implement logic for {functions[0] if functions else 'function'}")
            toga_code.append(f"        pass")
            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):
    if not file:
        return None, "Please upload a Gradio app Python file."
    
    try:
        code = file.decode('utf-8')
        components, functions = parse_gradio_code(code)
        
        if not components and functions and isinstance(functions[0], str):
            return None, f"Error: {functions[0]}"
        
        toga_code = generate_toga_code(components, functions)
        output_path = "/tmp/toga_app.py"
        with open(output_path, "w") as f:
            f.write(toga_code)
        
        return output_path, "Conversion successful! Download the Toga app below."
    except Exception as e:
        return 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) to convert it to a Toga desktop app.")
    file_input = gr.File(label="Upload Gradio App", file_types=[".py"])
    convert_button = gr.Button("Convert to Toga")
    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,
        outputs=[output, status]
    )

# Run Gradio directly 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)