""" Main application file for PyDeploy Studio Professional Gradio application for converting Python code to deployable Gradio apps Created by Veerakumar """ import gradio as gr import tempfile import os import sys from pathlib import Path # Add utils and components to path sys.path.append(str(Path(__file__).parent)) from utils.converter import convert_code from utils.deployer import deploy_to_space from utils.file_handler import ( create_zip_archive, read_file_content, parse_notebook, save_individual_file ) from utils.validator import validate_inputs from components.header import create_header from components.sidebar import create_sidebar from components.api_guide import create_api_guide from components.file_downloader import create_file_download_section # Constants MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB SUPPORTED_EXTENSIONS = ['.py', '.ipynb', '.txt'] class SpaceCreatorApp: """Main application class for PyDeploy Studio""" def __init__(self): self.temp_files = [] self.current_step = 1 self.generated_files = {} def cleanup(self): """Clean up temporary files""" for file_path in self.temp_files: try: if os.path.exists(file_path): os.remove(file_path) except: pass self.temp_files = [] def process_step1(self, input_text, input_file, groq_api_key): """Step 1: Convert code to Gradio app""" try: # Validate inputs validation_errors = validate_inputs(groq_api_key, None, None, "Convert Only") if validation_errors: return None, None, None, None, self._format_errors(validation_errors), 1 # Get source code code_content = self._extract_code(input_text, input_file) if not code_content.strip(): return None, None, None, None, "Please provide Python code to convert.", 1 # Convert code conversion_result = convert_code(code_content, groq_api_key) # Store generated files self.generated_files = { "app.py": conversion_result["app_py"], "requirements.txt": conversion_result["requirements_txt"], "README.md": conversion_result["readme_md"] } # Create individual files for download file_paths = {} for filename, content in self.generated_files.items(): file_path = save_individual_file(filename, content) self.temp_files.append(file_path) file_paths[filename] = file_path status = self._create_status_message("success", "Code conversion successful!") return (file_paths["app.py"], file_paths["requirements.txt"], file_paths["README.md"], None, status, 2) except Exception as e: error_msg = self._create_status_message("error", f"Conversion failed: {str(e)}") return None, None, None, None, error_msg, 1 def process_step2(self, hf_token, space_name, deploy_mode): """Step 2: Deploy to Hugging Face""" try: if deploy_mode != "Deploy to Hugging Face Space": status = self._create_status_message("info", "Skipping deployment as requested.") return None, status, 2 # Validate deployment inputs validation_errors = validate_inputs(None, hf_token, space_name, "Deploy") if validation_errors: return None, self._format_errors(validation_errors), 2 # Deploy to Hugging Face deploy_url = deploy_to_space(hf_token, space_name, self.generated_files) # Create zip archive of all files zip_bytes = create_zip_archive(self.generated_files) zip_path = save_individual_file("gradio_app_full_package.zip", zip_bytes, binary=True) self.temp_files.append(zip_path) status = self._create_status_message("success", f"Deployment successful! Space URL: {deploy_url}") return zip_path, status, 3 except Exception as e: error_msg = self._create_status_message("error", f"Deployment failed: {str(e)}") return None, error_msg, 2 def _extract_code(self, input_text, input_file): """Extract code from text or file""" code_content = "" if input_file is not None: file_content = read_file_content(input_file) if input_file.name.endswith('.ipynb') if hasattr(input_file, 'name') else False: with tempfile.NamedTemporaryFile(mode='w', suffix='.ipynb', delete=False, encoding='utf-8') as tmp: tmp.write(file_content) tmp_path = tmp.name try: code_content = parse_notebook(tmp_path) finally: os.unlink(tmp_path) else: code_content = file_content elif input_text.strip(): code_content = input_text return code_content def _format_errors(self, errors): """Format validation errors""" error_html = '
' return error_html def _create_status_message(self, msg_type, message): """Create formatted status message""" icons = { "success": "✓", "error": "✗", "warning": "⚠", "info": "ℹ" } colors = { "success": "#16a34a", "error": "#dc2626", "warning": "#d97706", "info": "#2563eb" } return f''' ''' def create_app(): """Create the Gradio application interface""" app = SpaceCreatorApp() # Custom theme with yellow and elephant grey theme = gr.themes.Base( primary_hue="yellow", secondary_hue="gray", neutral_hue="gray", font=("Inter", "Segoe UI", "Roboto", "Helvetica", "Arial", "sans-serif") ) with gr.Blocks( title="PyDeploy Studio", theme=theme, css=""" /* PyDeploy Studio CSS - Yellow & Elephant Grey Theme */ .gradio-container { max-width: 1300px !important; margin: 20px auto !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa !important; } body { background: #f1f3f4 !important; } /* Header with gradient */ .header-main { background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); color: white; padding: 2.5rem 2rem; border-radius: 12px; margin-bottom: 24px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); position: relative; overflow: hidden; } .header-main::before { content: ''; position: absolute; top: 0; right: 0; width: 200px; height: 200px; background: linear-gradient(45deg, #fbbf24 0%, transparent 70%); opacity: 0.1; border-radius: 50%; } .header-content { position: relative; z-index: 2; } .header-content h1 { margin: 0 0 0.5rem 0; font-size: 2.5rem; font-weight: 700; background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .header-content .subtitle { font-size: 1.1rem; color: #cbd5e1; margin-bottom: 1rem; } .creator-info { display: flex; align-items: center; gap: 10px; font-size: 0.9rem; color: #94a3b8; margin-top: 0.5rem; } .creator-info a { color: #fbbf24; text-decoration: none; font-weight: 500; transition: color 0.2s; } .creator-info a:hover { color: #f59e0b; text-decoration: underline; } .creator-info .linkedin-icon { color: #0077b5; } /* Main Layout */ .main-wrapper { display: flex; gap: 24px; margin-top: 20px; } /* Sidebar */ .sidebar-container { flex: 0 0 300px; background: white; padding: 1.75rem; border-radius: 12px; border: 1px solid #e5e7eb; box-shadow: 0 2px 10px rgba(0,0,0,0.05); height: fit-content; } .sidebar-title { font-size: 1.1rem; font-weight: 600; color: #374151; margin-bottom: 1.5rem; padding-bottom: 0.75rem; border-bottom: 2px solid #fbbf24; } /* Steps */ .step-container { margin-bottom: 2rem; } .step-item { display: flex; align-items: flex-start; gap: 1rem; padding: 1.25rem; margin-bottom: 0.75rem; background: #f9fafb; border-radius: 10px; border: 1px solid #e5e7eb; transition: all 0.3s ease; cursor: pointer; } .step-item:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); } .step-item.active { background: linear-gradient(135deg, #fef3c7 0%, #fffbeb 100%); border-color: #fbbf24; border-left: 4px solid #fbbf24; } .step-number { width: 32px; height: 32px; background: #e5e7eb; color: #6b7280; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 0.9rem; flex-shrink: 0; transition: all 0.3s ease; } .step-item.active .step-number { background: #fbbf24; color: #1f2937; } .step-content { flex: 1; } .step-title { font-weight: 600; color: #374151; margin-bottom: 0.25rem; } .step-item.active .step-title { color: #1f2937; } .step-description { font-size: 0.875rem; color: #6b7280; line-height: 1.5; } /* Tips Box */ .tips-container { background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border: 1px solid #e2e8f0; border-radius: 10px; padding: 1.5rem; } .tips-title { font-size: 1rem; font-weight: 600; color: #374151; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; } .tips-title::before { content: "💡"; } .tip-item { display: flex; align-items: flex-start; gap: 0.75rem; margin-bottom: 0.875rem; } .tip-item:last-child { margin-bottom: 0; } .tip-bullet { color: #fbbf24; font-weight: bold; flex-shrink: 0; margin-top: 0.125rem; } .tip-text { font-size: 0.875rem; color: #4b5563; line-height: 1.5; } /* Main Content */ .content-container { flex: 1; background: white; padding: 2rem; border-radius: 12px; border: 1px solid #e5e7eb; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } /* Step Groups */ .step-group { margin-bottom: 2rem; } .step-header { font-size: 1.5rem; font-weight: 600; color: #1f2937; margin-bottom: 1.5rem; padding-bottom: 0.75rem; border-bottom: 2px solid #fbbf24; } /* Form Elements */ .form-group { margin-bottom: 1.5rem; } .form-label { display: block; margin-bottom: 0.5rem; font-weight: 500; color: #374151; font-size: 0.95rem; } .form-info { font-size: 0.85rem; color: #6b7280; margin-top: 0.25rem; } input, textarea, select { width: 100%; padding: 0.875rem 1rem; border: 1px solid #d1d5db; border-radius: 8px; font-size: 0.95rem; font-family: 'Inter', sans-serif; transition: all 0.2s ease; color: #374151; } input:focus, textarea:focus, select:focus { outline: none; border-color: #fbbf24; box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.1); } input::placeholder, textarea::placeholder { color: #9ca3af; } /* Buttons */ button { font-family: 'Inter', sans-serif !important; font-weight: 500 !important; font-size: 0.95rem !important; padding: 0.875rem 1.75rem !important; border-radius: 8px !important; transition: all 0.3s ease !important; border: none !important; } button.primary { background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%) !important; color: #1f2937 !important; font-weight: 600 !important; } button.primary:hover { transform: translateY(-2px) !important; box-shadow: 0 4px 12px rgba(251, 191, 36, 0.3) !important; } button.secondary { background: white !important; color: #4b5563 !important; border: 1px solid #d1d5db !important; } button.secondary:hover { background: #f9fafb !important; border-color: #9ca3af !important; } /* Tabs */ .tab-nav { border-bottom: 1px solid #e5e7eb !important; margin-bottom: 1.5rem !important; } .tab-nav button { padding: 0.75rem 1.5rem !important; background: none !important; border: none !important; border-bottom: 3px solid transparent !important; color: #6b7280 !important; border-radius: 0 !important; } .tab-nav button.selected { color: #1f2937 !important; border-bottom-color: #fbbf24 !important; font-weight: 600 !important; } /* Code Editor */ #code_editor { font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace !important; font-size: 0.9rem !important; line-height: 1.6 !important; background: #f8fafc !important; border: 1px solid #e2e8f0 !important; border-radius: 8px !important; padding: 1.25rem !important; } /* Status Messages */ .status-message { padding: 1rem 1.25rem; border-radius: 8px; margin: 1.25rem 0; border-left: 4px solid; background: #f8fafc; font-size: 0.95rem; display: flex; align-items: flex-start; gap: 0.75rem; } .status-message.success { border-left-color: #16a34a; background: #f0fdf4; } .status-message.error { border-left-color: #dc2626; background: #fef2f2; } .status-message.warning { border-left-color: #d97706; background: #fffbeb; } .status-message.info { border-left-color: #2563eb; background: #eff6ff; } /* File Cards */ .file-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin: 1.5rem 0; } .file-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; padding: 1.5rem; transition: all 0.3s ease; } .file-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); border-color: #fbbf24; } .file-card-title { font-weight: 600; color: #1f2937; margin-bottom: 0.75rem; font-size: 1.1rem; } .file-card-description { font-size: 0.875rem; color: #6b7280; margin-bottom: 1rem; } /* Error Message */ .error-message { background: linear-gradient(135deg, #fef2f2 0%, #fff5f5 100%); border: 1px solid #fecaca; border-radius: 10px; padding: 1.5rem; margin: 1.5rem 0; } .error-message h4 { margin: 0 0 0.75rem 0; color: #dc2626; font-size: 1.1rem; display: flex; align-items: center; gap: 0.5rem; } .error-message h4::before { content: "⚠"; } .error-message ul { margin: 0; padding-left: 1.5rem; } .error-message li { margin-bottom: 0.5rem; color: #7f1d1d; } /* Radio Group */ .radio-group { display: flex; gap: 2rem; margin: 1.5rem 0; } .radio-item { display: flex; align-items: center; gap: 0.5rem; } /* Footer */ .footer { text-align: center; padding: 1.5rem; margin-top: 2rem; color: #6b7280; font-size: 0.875rem; border-top: 1px solid #e5e7eb; } .footer a { color: #f59e0b; text-decoration: none; } .footer a:hover { text-decoration: underline; } /* Responsive Design */ @media (max-width: 1024px) { .main-wrapper { flex-direction: column; } .sidebar-container { width: 100%; } .gradio-container { padding: 10px !important; } } @media (max-width: 768px) { .header-content h1 { font-size: 2rem; } .content-container { padding: 1.5rem; } .file-grid { grid-template-columns: 1fr; } .radio-group { flex-direction: column; gap: 1rem; } } /* Animation for active step */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .step-item.active { animation: pulse 2s infinite; } """ ) as demo: # Add fonts gr.HTML(''' ''') # Header with branding header_html = '''Your application is now live on Hugging Face Spaces