Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,21 +1,32 @@
|
|
1 |
-
from flask import Flask, request, render_template, send_file
|
2 |
import ast
|
3 |
import io
|
4 |
import zipfile
|
5 |
from datetime import datetime
|
|
|
|
|
|
|
|
|
6 |
|
7 |
app = Flask(__name__)
|
8 |
|
9 |
-
#
|
10 |
COMPONENT_MAP = {
|
11 |
'gr.Textbox': {'html_type': 'input', 'attributes': {'type': 'text', 'class': 'form-control'}},
|
12 |
-
'gr.Slider': {'html_type': 'input', 'attributes': {'type': 'range', 'class': 'form-range'}},
|
13 |
'gr.Image': {'html_type': 'input', 'attributes': {'type': 'file', 'accept': 'image/*', 'class': 'form-control'}},
|
14 |
'gr.Dropdown': {'html_type': 'select', 'attributes': {'class': 'form-select'}},
|
15 |
'gr.Checkbox': {'html_type': 'input', 'attributes': {'type': 'checkbox', 'class': 'form-check-input'}},
|
16 |
'gr.Radio': {'html_type': 'input', 'attributes': {'type': 'radio', 'class': 'form-check-input'}},
|
17 |
'gr.Label': {'html_type': 'div', 'attributes': {'class': 'output-label'}},
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
}
|
20 |
|
21 |
def parse_gradio_script(script):
|
@@ -25,7 +36,6 @@ def parse_gradio_script(script):
|
|
25 |
|
26 |
for node in ast.walk(tree):
|
27 |
if isinstance(node, ast.Call) and hasattr(node.func, 'attr'):
|
28 |
-
# Detect Interface or Blocks
|
29 |
if node.func.attr == 'Interface':
|
30 |
components['is_blocks'] = False
|
31 |
for kw in node.keywords:
|
@@ -43,8 +53,6 @@ def parse_gradio_script(script):
|
|
43 |
components['outputs'] = [ast.unparse(kw.value)]
|
44 |
elif node.func.attr == 'Blocks':
|
45 |
components['is_blocks'] = True
|
46 |
-
# For Blocks, we'll need to parse the context (simplified here)
|
47 |
-
# This assumes inputs/outputs are defined in event listeners
|
48 |
for subnode in ast.walk(node):
|
49 |
if isinstance(subnode, ast.Call) and hasattr(subnode.func, 'attr') and subnode.func.attr == 'click':
|
50 |
components['function'] = ast.unparse(subnode.args[0])
|
@@ -59,6 +67,7 @@ def generate_flask_app(script, components):
|
|
59 |
import io
|
60 |
from PIL import Image
|
61 |
import base64
|
|
|
62 |
|
63 |
app = Flask(__name__)
|
64 |
|
@@ -82,6 +91,20 @@ def index():
|
|
82 |
if file:
|
83 |
img = Image.open(file)
|
84 |
inputs.append(img)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
"""
|
86 |
else:
|
87 |
flask_code += f"""
|
@@ -110,6 +133,7 @@ def index():
|
|
110 |
.form-group { margin-bottom: 20px; }
|
111 |
.btn-submit { width: 100%; }
|
112 |
.output { margin-top: 20px; padding: 15px; background: #e9ecef; border-radius: 5px; }
|
|
|
113 |
</style>
|
114 |
</head>
|
115 |
<body>
|
@@ -129,11 +153,14 @@ def index():
|
|
129 |
<{comp_info['html_type']} name="input_{i}" id="input_{i}" {attrs}>
|
130 |
"""
|
131 |
if comp_type == 'gr.Dropdown':
|
132 |
-
# Simplified dropdown options (could be parsed from script)
|
133 |
html_template += """
|
134 |
<option value="option1">Option 1</option>
|
135 |
<option value="option2">Option 2</option>
|
136 |
</select>
|
|
|
|
|
|
|
|
|
137 |
"""
|
138 |
else:
|
139 |
html_template += f"""
|
@@ -162,6 +189,24 @@ def index():
|
|
162 |
{% if result %}
|
163 |
<img src="data:image/png;base64,{{ result|to_base64 }}" alt="Output Image" class="img-fluid">
|
164 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
"""
|
166 |
else:
|
167 |
html_template += """
|
@@ -179,42 +224,91 @@ def index():
|
|
179 |
</html>
|
180 |
"""
|
181 |
|
182 |
-
# Add
|
183 |
flask_code += """
|
184 |
@app.template_filter('to_base64')
|
185 |
-
def to_base64(
|
186 |
-
if isinstance(
|
187 |
buffered = io.BytesIO()
|
188 |
-
|
189 |
return base64.b64encode(buffered.getvalue()).decode('utf-8')
|
190 |
-
|
|
|
|
|
191 |
"""
|
192 |
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
|
202 |
@app.route('/', methods=['GET', 'POST'])
|
203 |
def convert_gradio():
|
|
|
|
|
|
|
204 |
if request.method == 'POST':
|
205 |
gradio_script = request.form['gradio_script']
|
206 |
try:
|
207 |
components = parse_gradio_script(gradio_script)
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
)
|
215 |
except Exception as e:
|
216 |
return render_template('index.html', error=str(e))
|
217 |
return render_template('index.html')
|
218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
if __name__ == '__main__':
|
220 |
app.run(host="0.0.0.0", port=7860, debug=True)
|
|
|
1 |
+
from flask import Flask, request, render_template, send_file, Response
|
2 |
import ast
|
3 |
import io
|
4 |
import zipfile
|
5 |
from datetime import datetime
|
6 |
+
import subprocess
|
7 |
+
import time
|
8 |
+
import os
|
9 |
+
import threading
|
10 |
|
11 |
app = Flask(__name__)
|
12 |
|
13 |
+
# Expanded mapping of Gradio components to Flask-compatible HTML inputs
|
14 |
COMPONENT_MAP = {
|
15 |
'gr.Textbox': {'html_type': 'input', 'attributes': {'type': 'text', 'class': 'form-control'}},
|
16 |
+
'gr.Slider': {'html_type': 'input', 'attributes': {'type': 'range', 'class': 'form-range', 'min': '0', 'max': '100'}},
|
17 |
'gr.Image': {'html_type': 'input', 'attributes': {'type': 'file', 'accept': 'image/*', 'class': 'form-control'}},
|
18 |
'gr.Dropdown': {'html_type': 'select', 'attributes': {'class': 'form-select'}},
|
19 |
'gr.Checkbox': {'html_type': 'input', 'attributes': {'type': 'checkbox', 'class': 'form-check-input'}},
|
20 |
'gr.Radio': {'html_type': 'input', 'attributes': {'type': 'radio', 'class': 'form-check-input'}},
|
21 |
'gr.Label': {'html_type': 'div', 'attributes': {'class': 'output-label'}},
|
22 |
+
'gr.Audio': {'html_type': 'input', 'attributes': {'type': 'file', 'accept': 'audio/*', 'class': 'form-control'}},
|
23 |
+
'gr.Video': {'html_type': 'input', 'attributes': {'type': 'file', 'accept': 'video/*', 'class': 'form-control'}},
|
24 |
+
'gr.File': {'html_type': 'input', 'attributes': {'type': 'file', 'class': 'form-control'}},
|
25 |
+
'gr.Number': {'html_type': 'input', 'attributes': {'type': 'number', 'class': 'form-control'}},
|
26 |
+
'gr.ColorPicker': {'html_type': 'input', 'attributes': {'type': 'color', 'class': 'form-control'}},
|
27 |
+
'gr.Markdown': {'html_type': 'div', 'attributes': {'class': 'markdown-output'}}, # For static text output
|
28 |
+
'gr.Dataframe': {'html_type': 'textarea', 'attributes': {'class': 'form-control', 'readonly': 'true'}}, # Simplified output
|
29 |
+
# Add more components as needed
|
30 |
}
|
31 |
|
32 |
def parse_gradio_script(script):
|
|
|
36 |
|
37 |
for node in ast.walk(tree):
|
38 |
if isinstance(node, ast.Call) and hasattr(node.func, 'attr'):
|
|
|
39 |
if node.func.attr == 'Interface':
|
40 |
components['is_blocks'] = False
|
41 |
for kw in node.keywords:
|
|
|
53 |
components['outputs'] = [ast.unparse(kw.value)]
|
54 |
elif node.func.attr == 'Blocks':
|
55 |
components['is_blocks'] = True
|
|
|
|
|
56 |
for subnode in ast.walk(node):
|
57 |
if isinstance(subnode, ast.Call) and hasattr(subnode.func, 'attr') and subnode.func.attr == 'click':
|
58 |
components['function'] = ast.unparse(subnode.args[0])
|
|
|
67 |
import io
|
68 |
from PIL import Image
|
69 |
import base64
|
70 |
+
import pandas as pd
|
71 |
|
72 |
app = Flask(__name__)
|
73 |
|
|
|
91 |
if file:
|
92 |
img = Image.open(file)
|
93 |
inputs.append(img)
|
94 |
+
"""
|
95 |
+
elif comp_type == 'gr.Audio' or comp_type == 'gr.Video' or comp_type == 'gr.File':
|
96 |
+
flask_code += f"""
|
97 |
+
file = request.files.get('input_{i}')
|
98 |
+
if file:
|
99 |
+
inputs.append(file.read())
|
100 |
+
"""
|
101 |
+
elif comp_type == 'gr.Checkbox':
|
102 |
+
flask_code += f"""
|
103 |
+
inputs.append(request.form.get('input_{i}') == 'on')
|
104 |
+
"""
|
105 |
+
elif comp_type == 'gr.Number':
|
106 |
+
flask_code += f"""
|
107 |
+
inputs.append(float(request.form.get('input_{i}', 0)))
|
108 |
"""
|
109 |
else:
|
110 |
flask_code += f"""
|
|
|
133 |
.form-group { margin-bottom: 20px; }
|
134 |
.btn-submit { width: 100%; }
|
135 |
.output { margin-top: 20px; padding: 15px; background: #e9ecef; border-radius: 5px; }
|
136 |
+
.markdown-output { white-space: pre-wrap; }
|
137 |
</style>
|
138 |
</head>
|
139 |
<body>
|
|
|
153 |
<{comp_info['html_type']} name="input_{i}" id="input_{i}" {attrs}>
|
154 |
"""
|
155 |
if comp_type == 'gr.Dropdown':
|
|
|
156 |
html_template += """
|
157 |
<option value="option1">Option 1</option>
|
158 |
<option value="option2">Option 2</option>
|
159 |
</select>
|
160 |
+
"""
|
161 |
+
elif comp_type == 'gr.Radio':
|
162 |
+
html_template += f"""
|
163 |
+
</{comp_info['html_type']}> <label class="form-check-label" for="input_{i}">Option</label>
|
164 |
"""
|
165 |
else:
|
166 |
html_template += f"""
|
|
|
189 |
{% if result %}
|
190 |
<img src="data:image/png;base64,{{ result|to_base64 }}" alt="Output Image" class="img-fluid">
|
191 |
{% endif %}
|
192 |
+
"""
|
193 |
+
elif comp_type == 'gr.Audio':
|
194 |
+
html_template += """
|
195 |
+
{% if result %}
|
196 |
+
<audio controls><source src="data:audio/wav;base64,{{ result|to_base64 }}" type="audio/wav"></audio>
|
197 |
+
{% endif %}
|
198 |
+
"""
|
199 |
+
elif comp_type == 'gr.Video':
|
200 |
+
html_template += """
|
201 |
+
{% if result %}
|
202 |
+
<video controls><source src="data:video/mp4;base64,{{ result|to_base64 }}" type="video/mp4"></video>
|
203 |
+
{% endif %}
|
204 |
+
"""
|
205 |
+
elif comp_type == 'gr.Dataframe':
|
206 |
+
html_template += """
|
207 |
+
{% if result %}
|
208 |
+
<pre>{{ result.to_html() if result.__class__.__name__ == 'DataFrame' else result }}</pre>
|
209 |
+
{% endif %}
|
210 |
"""
|
211 |
else:
|
212 |
html_template += """
|
|
|
224 |
</html>
|
225 |
"""
|
226 |
|
227 |
+
# Add filters for base64 encoding
|
228 |
flask_code += """
|
229 |
@app.template_filter('to_base64')
|
230 |
+
def to_base64(data):
|
231 |
+
if isinstance(data, Image.Image):
|
232 |
buffered = io.BytesIO()
|
233 |
+
data.save(buffered, format="PNG")
|
234 |
return base64.b64encode(buffered.getvalue()).decode('utf-8')
|
235 |
+
elif isinstance(data, bytes):
|
236 |
+
return base64.b64encode(data).decode('utf-8')
|
237 |
+
return str(data)
|
238 |
"""
|
239 |
|
240 |
+
requirements = "flask\npillow\npandas\n"
|
241 |
+
|
242 |
+
return flask_code, html_template, requirements
|
243 |
+
|
244 |
+
def run_preview(flask_code, port=5001):
|
245 |
+
"""Run the generated Flask app in a subprocess for preview."""
|
246 |
+
with open('temp_app.py', 'w') as f:
|
247 |
+
f.write(flask_code)
|
248 |
+
os.makedirs('templates', exist_ok=True)
|
249 |
+
with open('templates/index.html', 'w') as f:
|
250 |
+
f.write(html_template)
|
251 |
+
|
252 |
+
proc = subprocess.Popen(['python', 'temp_app.py', str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
253 |
+
time.sleep(2) # Give it time to start
|
254 |
+
return proc, f'http://localhost:{port}'
|
255 |
|
256 |
@app.route('/', methods=['GET', 'POST'])
|
257 |
def convert_gradio():
|
258 |
+
global preview_process
|
259 |
+
preview_process = None
|
260 |
+
|
261 |
if request.method == 'POST':
|
262 |
gradio_script = request.form['gradio_script']
|
263 |
try:
|
264 |
components = parse_gradio_script(gradio_script)
|
265 |
+
flask_code, html_template, requirements = generate_flask_app(gradio_script, components)
|
266 |
+
|
267 |
+
# Create ZIP file
|
268 |
+
buffer = io.BytesIO()
|
269 |
+
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
270 |
+
zip_file.writestr('app.py', flask_code)
|
271 |
+
zip_file.writestr('templates/index.html', html_template)
|
272 |
+
zip_file.writestr('requirements.txt', requirements)
|
273 |
+
buffer.seek(0)
|
274 |
+
|
275 |
+
# Start preview in a subprocess
|
276 |
+
preview_process, preview_url = run_preview(flask_code)
|
277 |
+
|
278 |
+
return render_template(
|
279 |
+
'result.html',
|
280 |
+
flask_code=flask_code,
|
281 |
+
html_template=html_template,
|
282 |
+
requirements=requirements,
|
283 |
+
zip_buffer=buffer.getvalue(),
|
284 |
+
preview_url=preview_url
|
285 |
)
|
286 |
except Exception as e:
|
287 |
return render_template('index.html', error=str(e))
|
288 |
return render_template('index.html')
|
289 |
|
290 |
+
@app.route('/download/<filename>')
|
291 |
+
def download_file(filename):
|
292 |
+
if filename == 'app.py':
|
293 |
+
content = request.args.get('content')
|
294 |
+
return Response(content, mimetype='text/plain', headers={"Content-Disposition": f"attachment;filename={filename}"})
|
295 |
+
elif filename == 'index.html':
|
296 |
+
content = request.args.get('content')
|
297 |
+
return Response(content, mimetype='text/html', headers={"Content-Disposition": f"attachment;filename={filename}"})
|
298 |
+
elif filename == 'requirements.txt':
|
299 |
+
content = request.args.get('content')
|
300 |
+
return Response(content, mimetype='text/plain', headers={"Content-Disposition": f"attachment;filename={filename}"})
|
301 |
+
elif filename == 'zip':
|
302 |
+
buffer = io.BytesIO(request.args.get('content').encode())
|
303 |
+
buffer.seek(0)
|
304 |
+
return send_file(buffer, mimetype='application/zip', as_attachment=True, download_name=f'flask_app_{datetime.now().strftime("%Y%m%d_%H%M%S")}.zip')
|
305 |
+
return "File not found", 404
|
306 |
+
|
307 |
+
@app.teardown_appcontext
|
308 |
+
def cleanup(exc):
|
309 |
+
global preview_process
|
310 |
+
if preview_process:
|
311 |
+
preview_process.terminate()
|
312 |
+
|
313 |
if __name__ == '__main__':
|
314 |
app.run(host="0.0.0.0", port=7860, debug=True)
|