""" Main application file for PyDeploy Studio Professional Gradio application for converting Python code to deployable Gradio apps Created by Veerakumar C B """ 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: #fbbf24; /* Changed to yellow */ 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