update requirements for gradio and streamlit
Browse files
app.py
CHANGED
@@ -2030,10 +2030,161 @@ This will help me create a better design for you."""
|
|
2030 |
|
2031 |
# Deploy to Spaces logic
|
2032 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2033 |
def wrap_html_in_gradio_app(html_code):
|
2034 |
# Escape triple quotes for safe embedding
|
2035 |
safe_html = html_code.replace('"""', r'\"\"\"')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2036 |
return (
|
|
|
2037 |
'import gradio as gr\n\n'
|
2038 |
'def show_html():\n'
|
2039 |
f' return """{safe_html}"""\n\n'
|
@@ -2559,8 +2710,36 @@ with gr.Blocks(
|
|
2559 |
exist_ok=True
|
2560 |
)
|
2561 |
|
2562 |
-
#
|
|
|
|
|
|
|
2563 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2564 |
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as f:
|
2565 |
f.write(code)
|
2566 |
temp_path = f.name
|
@@ -2817,8 +2996,37 @@ with gr.Blocks(
|
|
2817 |
import os
|
2818 |
os.unlink(temp_path)
|
2819 |
else:
|
2820 |
-
|
|
|
|
|
|
|
2821 |
import tempfile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2822 |
with tempfile.NamedTemporaryFile("w", suffix=f".{file_name.split('.')[-1]}", delete=False) as f:
|
2823 |
f.write(code)
|
2824 |
temp_path = f.name
|
|
|
2030 |
|
2031 |
# Deploy to Spaces logic
|
2032 |
|
2033 |
+
def extract_import_statements(code):
|
2034 |
+
"""Extract import statements from generated code."""
|
2035 |
+
import ast
|
2036 |
+
import re
|
2037 |
+
|
2038 |
+
import_statements = []
|
2039 |
+
|
2040 |
+
# Built-in Python modules to exclude
|
2041 |
+
builtin_modules = {
|
2042 |
+
'os', 'sys', 'json', 'time', 'datetime', 'random', 'math', 're', 'collections',
|
2043 |
+
'itertools', 'functools', 'pathlib', 'urllib', 'http', 'email', 'html', 'xml',
|
2044 |
+
'csv', 'tempfile', 'shutil', 'subprocess', 'threading', 'multiprocessing',
|
2045 |
+
'asyncio', 'logging', 'typing', 'base64', 'hashlib', 'secrets', 'uuid',
|
2046 |
+
'copy', 'pickle', 'io', 'contextlib', 'warnings', 'sqlite3', 'gzip', 'zipfile',
|
2047 |
+
'tarfile', 'socket', 'ssl', 'platform', 'getpass', 'pwd', 'grp', 'stat',
|
2048 |
+
'glob', 'fnmatch', 'linecache', 'traceback', 'inspect', 'keyword', 'token',
|
2049 |
+
'tokenize', 'ast', 'code', 'codeop', 'dis', 'py_compile', 'compileall',
|
2050 |
+
'importlib', 'pkgutil', 'modulefinder', 'runpy', 'site', 'sysconfig'
|
2051 |
+
}
|
2052 |
+
|
2053 |
+
try:
|
2054 |
+
# Try to parse as Python AST
|
2055 |
+
tree = ast.parse(code)
|
2056 |
+
|
2057 |
+
for node in ast.walk(tree):
|
2058 |
+
if isinstance(node, ast.Import):
|
2059 |
+
for alias in node.names:
|
2060 |
+
module_name = alias.name.split('.')[0]
|
2061 |
+
if module_name not in builtin_modules and not module_name.startswith('_'):
|
2062 |
+
import_statements.append(f"import {alias.name}")
|
2063 |
+
|
2064 |
+
elif isinstance(node, ast.ImportFrom):
|
2065 |
+
if node.module:
|
2066 |
+
module_name = node.module.split('.')[0]
|
2067 |
+
if module_name not in builtin_modules and not module_name.startswith('_'):
|
2068 |
+
names = [alias.name for alias in node.names]
|
2069 |
+
import_statements.append(f"from {node.module} import {', '.join(names)}")
|
2070 |
+
|
2071 |
+
except SyntaxError:
|
2072 |
+
# Fallback: use regex to find import statements
|
2073 |
+
for line in code.split('\n'):
|
2074 |
+
line = line.strip()
|
2075 |
+
if line.startswith('import ') or line.startswith('from '):
|
2076 |
+
# Check if it's not a builtin module
|
2077 |
+
if line.startswith('import '):
|
2078 |
+
module_name = line.split()[1].split('.')[0]
|
2079 |
+
elif line.startswith('from '):
|
2080 |
+
module_name = line.split()[1].split('.')[0]
|
2081 |
+
|
2082 |
+
if module_name not in builtin_modules and not module_name.startswith('_'):
|
2083 |
+
import_statements.append(line)
|
2084 |
+
|
2085 |
+
return list(set(import_statements)) # Remove duplicates
|
2086 |
+
|
2087 |
+
def generate_requirements_txt_with_llm(import_statements):
|
2088 |
+
"""Generate requirements.txt content using LLM based on import statements."""
|
2089 |
+
if not import_statements:
|
2090 |
+
return "# No additional dependencies required\n"
|
2091 |
+
|
2092 |
+
# Use a lightweight model for this task
|
2093 |
+
try:
|
2094 |
+
client = get_inference_client("Qwen/Qwen3-Coder-480B-A35B", "auto")
|
2095 |
+
|
2096 |
+
imports_text = '\n'.join(import_statements)
|
2097 |
+
|
2098 |
+
prompt = f"""Based on the following Python import statements, generate a requirements.txt file with the necessary PyPI packages:
|
2099 |
+
|
2100 |
+
{imports_text}
|
2101 |
+
|
2102 |
+
Instructions:
|
2103 |
+
- Only include external packages that need to be installed via pip
|
2104 |
+
- Do not include Python built-in modules
|
2105 |
+
- Use the correct PyPI package names (e.g., cv2 -> opencv-python, PIL -> Pillow, sklearn -> scikit-learn)
|
2106 |
+
- Do not specify versions unless absolutely necessary for compatibility
|
2107 |
+
- One package per line
|
2108 |
+
- If no external packages are needed, return "# No additional dependencies required"
|
2109 |
+
|
2110 |
+
Requirements.txt:"""
|
2111 |
+
|
2112 |
+
messages = [
|
2113 |
+
{"role": "system", "content": "You are a Python packaging expert. Generate accurate requirements.txt files based on import statements."},
|
2114 |
+
{"role": "user", "content": prompt}
|
2115 |
+
]
|
2116 |
+
|
2117 |
+
response = client.chat.completions.create(
|
2118 |
+
model="Qwen/Qwen3-Coder-480B-A35B",
|
2119 |
+
messages=messages,
|
2120 |
+
max_tokens=1024,
|
2121 |
+
temperature=0.1
|
2122 |
+
)
|
2123 |
+
|
2124 |
+
requirements_content = response.choices[0].message.content.strip()
|
2125 |
+
|
2126 |
+
# Clean up the response in case it includes extra formatting
|
2127 |
+
if '```' in requirements_content:
|
2128 |
+
# Extract content between code blocks
|
2129 |
+
lines = requirements_content.split('\n')
|
2130 |
+
in_code_block = False
|
2131 |
+
clean_lines = []
|
2132 |
+
for line in lines:
|
2133 |
+
if line.strip().startswith('```'):
|
2134 |
+
in_code_block = not in_code_block
|
2135 |
+
continue
|
2136 |
+
if in_code_block:
|
2137 |
+
clean_lines.append(line)
|
2138 |
+
requirements_content = '\n'.join(clean_lines).strip()
|
2139 |
+
|
2140 |
+
# Ensure it ends with a newline
|
2141 |
+
if requirements_content and not requirements_content.endswith('\n'):
|
2142 |
+
requirements_content += '\n'
|
2143 |
+
|
2144 |
+
return requirements_content if requirements_content else "# No additional dependencies required\n"
|
2145 |
+
|
2146 |
+
except Exception as e:
|
2147 |
+
# Fallback: simple extraction with basic mapping
|
2148 |
+
dependencies = set()
|
2149 |
+
special_cases = {
|
2150 |
+
'cv2': 'opencv-python',
|
2151 |
+
'PIL': 'Pillow',
|
2152 |
+
'sklearn': 'scikit-learn',
|
2153 |
+
'skimage': 'scikit-image',
|
2154 |
+
'bs4': 'beautifulsoup4'
|
2155 |
+
}
|
2156 |
+
|
2157 |
+
for stmt in import_statements:
|
2158 |
+
if stmt.startswith('import '):
|
2159 |
+
module_name = stmt.split()[1].split('.')[0]
|
2160 |
+
package_name = special_cases.get(module_name, module_name)
|
2161 |
+
dependencies.add(package_name)
|
2162 |
+
elif stmt.startswith('from '):
|
2163 |
+
module_name = stmt.split()[1].split('.')[0]
|
2164 |
+
package_name = special_cases.get(module_name, module_name)
|
2165 |
+
dependencies.add(package_name)
|
2166 |
+
|
2167 |
+
if dependencies:
|
2168 |
+
return '\n'.join(sorted(dependencies)) + '\n'
|
2169 |
+
else:
|
2170 |
+
return "# No additional dependencies required\n"
|
2171 |
+
|
2172 |
def wrap_html_in_gradio_app(html_code):
|
2173 |
# Escape triple quotes for safe embedding
|
2174 |
safe_html = html_code.replace('"""', r'\"\"\"')
|
2175 |
+
|
2176 |
+
# Extract import statements and generate requirements.txt with LLM
|
2177 |
+
import_statements = extract_import_statements(html_code)
|
2178 |
+
requirements_comment = ""
|
2179 |
+
if import_statements:
|
2180 |
+
requirements_content = generate_requirements_txt_with_llm(import_statements)
|
2181 |
+
requirements_comment = (
|
2182 |
+
"# Generated requirements.txt content (create this file manually if needed):\n"
|
2183 |
+
+ '\n'.join(f"# {line}" for line in requirements_content.strip().split('\n')) + '\n\n'
|
2184 |
+
)
|
2185 |
+
|
2186 |
return (
|
2187 |
+
f'{requirements_comment}'
|
2188 |
'import gradio as gr\n\n'
|
2189 |
'def show_html():\n'
|
2190 |
f' return """{safe_html}"""\n\n'
|
|
|
2710 |
exist_ok=True
|
2711 |
)
|
2712 |
|
2713 |
+
# Generate and upload requirements.txt for Streamlit apps
|
2714 |
+
import_statements = extract_import_statements(code)
|
2715 |
+
requirements_content = generate_requirements_txt_with_llm(import_statements)
|
2716 |
+
|
2717 |
import tempfile
|
2718 |
+
|
2719 |
+
# Upload requirements.txt first
|
2720 |
+
try:
|
2721 |
+
with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as f:
|
2722 |
+
f.write(requirements_content)
|
2723 |
+
requirements_temp_path = f.name
|
2724 |
+
|
2725 |
+
api.upload_file(
|
2726 |
+
path_or_fileobj=requirements_temp_path,
|
2727 |
+
path_in_repo="requirements.txt",
|
2728 |
+
repo_id=repo_id,
|
2729 |
+
repo_type="space"
|
2730 |
+
)
|
2731 |
+
except Exception as e:
|
2732 |
+
error_msg = str(e)
|
2733 |
+
if "403 Forbidden" in error_msg and "write token" in error_msg:
|
2734 |
+
return gr.update(value=f"Error uploading requirements.txt: Permission denied. Please ensure you have write access to {repo_id} and your token has the correct permissions.", visible=True)
|
2735 |
+
else:
|
2736 |
+
return gr.update(value=f"Error uploading requirements.txt: {e}", visible=True)
|
2737 |
+
finally:
|
2738 |
+
import os
|
2739 |
+
if 'requirements_temp_path' in locals():
|
2740 |
+
os.unlink(requirements_temp_path)
|
2741 |
+
|
2742 |
+
# Upload the user's code to src/streamlit_app.py (for both new and existing spaces)
|
2743 |
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as f:
|
2744 |
f.write(code)
|
2745 |
temp_path = f.name
|
|
|
2996 |
import os
|
2997 |
os.unlink(temp_path)
|
2998 |
else:
|
2999 |
+
# Generate and upload requirements.txt for Gradio apps
|
3000 |
+
import_statements = extract_import_statements(code)
|
3001 |
+
requirements_content = generate_requirements_txt_with_llm(import_statements)
|
3002 |
+
|
3003 |
import tempfile
|
3004 |
+
|
3005 |
+
# Upload requirements.txt first
|
3006 |
+
try:
|
3007 |
+
with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as f:
|
3008 |
+
f.write(requirements_content)
|
3009 |
+
requirements_temp_path = f.name
|
3010 |
+
|
3011 |
+
api.upload_file(
|
3012 |
+
path_or_fileobj=requirements_temp_path,
|
3013 |
+
path_in_repo="requirements.txt",
|
3014 |
+
repo_id=repo_id,
|
3015 |
+
repo_type="space"
|
3016 |
+
)
|
3017 |
+
except Exception as e:
|
3018 |
+
error_msg = str(e)
|
3019 |
+
if "403 Forbidden" in error_msg and "write token" in error_msg:
|
3020 |
+
return gr.update(value=f"Error uploading requirements.txt: Permission denied. Please ensure you have write access to {repo_id} and your token has the correct permissions.", visible=True)
|
3021 |
+
else:
|
3022 |
+
return gr.update(value=f"Error uploading requirements.txt: {e}", visible=True)
|
3023 |
+
finally:
|
3024 |
+
import os
|
3025 |
+
if 'requirements_temp_path' in locals():
|
3026 |
+
os.unlink(requirements_temp_path)
|
3027 |
+
|
3028 |
+
# Now upload the main app.py file
|
3029 |
+
file_name = "app.py"
|
3030 |
with tempfile.NamedTemporaryFile("w", suffix=f".{file_name.split('.')[-1]}", delete=False) as f:
|
3031 |
f.write(code)
|
3032 |
temp_path = f.name
|