CoolFace
Apppublic

akhaliq/anycoder

sourceHugging Faceupdated 5mo agoView on Hugging Face
3.3klikes
backend_deploy.py1704 linesDownload Raw Back to root
1"""2Standalone deployment utilities for publishing to HuggingFace Spaces.3No Gradio dependencies - can be used in backend API.4"""5import os6import re7import json8import uuid9import tempfile10import shutil11import ast12from typing import Dict, List, Optional, Tuple13from pathlib import Path14 15from huggingface_hub import HfApi16from backend_models import get_inference_client, get_real_model_id17from backend_parsers import (18    parse_transformers_js_output,19    parse_html_code,20    parse_python_requirements,21    parse_multi_file_python_output,22    parse_react_output,23    strip_tool_call_markers,24    remove_code_block,25    extract_import_statements,26    generate_requirements_txt_with_llm,27    enforce_critical_versions28)29 30 31def prettify_comfyui_json_for_html(json_content: str) -> str:32    """Convert ComfyUI JSON to stylized HTML display with download button"""33    try:34        # Parse and prettify the JSON35        parsed_json = json.loads(json_content)36        prettified_json = json.dumps(parsed_json, indent=2, ensure_ascii=False)37        38        # Create Apple-style HTML wrapper39        html_content = f"""<!DOCTYPE html>40<html lang="en">41<head>42    <meta charset="UTF-8">43    <meta name="viewport" content="width=device-width, initial-scale=1.0">44    <title>ComfyUI Workflow</title>45    <style>46        * {{47            margin: 0;48            padding: 0;49            box-sizing: border-box;50        }}51        body {{52            font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;53            background-color: #000000;54            color: #f5f5f7;55            line-height: 1.6;56            padding: 20px;57            min-height: 100vh;58        }}59        .container {{60            max-width: 1200px;61            margin: 0 auto;62        }}63        .header {{64            text-align: center;65            margin-bottom: 40px;66            padding: 40px 20px;67        }}68        .header h1 {{69            font-size: 48px;70            font-weight: 600;71            color: #ffffff;72            margin-bottom: 12px;73            letter-spacing: -0.02em;74        }}75        .header p {{76            font-size: 18px;77            color: #86868b;78            font-weight: 400;79        }}80        .controls {{81            display: flex;82            gap: 12px;83            margin-bottom: 24px;84            justify-content: center;85        }}86        .btn {{87            padding: 12px 24px;88            border: none;89            border-radius: 24px;90            font-size: 14px;91            font-weight: 500;92            cursor: pointer;93            transition: all 0.2s;94            font-family: inherit;95        }}96        .btn-primary {{97            background: #ffffff;98            color: #000000;99        }}100        .btn-primary:hover {{101            background: #f5f5f7;102            transform: scale(0.98);103        }}104        .btn-secondary {{105            background: #1d1d1f;106            color: #f5f5f7;107            border: 1px solid #424245;108        }}109        .btn-secondary:hover {{110            background: #2d2d2f;111            transform: scale(0.98);112        }}113        .json-container {{114            background-color: #1d1d1f;115            border-radius: 16px;116            padding: 32px;117            overflow-x: auto;118            border: 1px solid #424245;119            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);120        }}121        pre {{122            margin: 0;123            font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;124            font-size: 13px;125            line-height: 1.6;126            white-space: pre-wrap;127            word-wrap: break-word;128        }}129        .json-key {{130            color: #9cdcfe;131        }}132        .json-string {{133            color: #ce9178;134        }}135        .json-number {{136            color: #b5cea8;137        }}138        .json-boolean {{139            color: #569cd6;140        }}141        .json-null {{142            color: #569cd6;143        }}144        .success {{145            color: #30d158;146        }}147        @media (max-width: 768px) {{148            .header h1 {{149                font-size: 32px;150            }}151            .controls {{152                flex-direction: column;153            }}154            .json-container {{155                padding: 20px;156            }}157        }}158    </style>159</head>160<body>161    <div class="container">162        <div class="header">163            <h1>ComfyUI Workflow</h1>164            <p>View and download your workflow JSON</p>165        </div>166        167        <div class="controls">168            <button class="btn btn-primary" onclick="downloadJSON()">Download JSON</button>169            <button class="btn btn-secondary" onclick="copyToClipboard()">Copy to Clipboard</button>170        </div>171        172        <div class="json-container">173            <pre id="json-content">{prettified_json}</pre>174        </div>175    </div>176 177    <script>178        function copyToClipboard() {{179            const jsonContent = document.getElementById('json-content').textContent;180            navigator.clipboard.writeText(jsonContent).then(() => {{181                const btn = event.target;182                const originalText = btn.textContent;183                btn.textContent = 'Copied!';184                btn.classList.add('success');185                setTimeout(() => {{186                    btn.textContent = originalText;187                    btn.classList.remove('success');188                }}, 2000);189            }}).catch(err => {{190                alert('Failed to copy to clipboard');191            }});192        }}193 194        function downloadJSON() {{195            const jsonContent = document.getElementById('json-content').textContent;196            const blob = new Blob([jsonContent], {{ type: 'application/json' }});197            const url = URL.createObjectURL(blob);198            const a = document.createElement('a');199            a.href = url;200            a.download = 'comfyui_workflow.json';201            document.body.appendChild(a);202            a.click();203            document.body.removeChild(a);204            URL.revokeObjectURL(url);205            206            const btn = event.target;207            const originalText = btn.textContent;208            btn.textContent = 'Downloaded!';209            btn.classList.add('success');210            setTimeout(() => {{211                btn.textContent = originalText;212                btn.classList.remove('success');213            }}, 2000);214        }}215 216        // Add syntax highlighting217        function highlightJSON() {{218            const content = document.getElementById('json-content');219            let html = content.innerHTML;220            221            // Highlight different JSON elements222            html = html.replace(/"([^"]+)":/g, '<span class="json-key">"$1":</span>');223            html = html.replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>');224            html = html.replace(/: (-?\\d+\\.?\\d*)/g, ': <span class="json-number">$1</span>');225            html = html.replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>');226            html = html.replace(/: null/g, ': <span class="json-null">null</span>');227            228            content.innerHTML = html;229        }}230 231        // Apply syntax highlighting after page load232        window.addEventListener('load', highlightJSON);233    </script>234</body>235</html>"""236        return html_content237    except json.JSONDecodeError:238        # If it's not valid JSON, return as-is wrapped in basic HTML239        return f"""<!DOCTYPE html>240<html lang="en">241<head>242    <meta charset="UTF-8">243    <meta name="viewport" content="width=device-width, initial-scale=1.0">244    <title>ComfyUI Workflow</title>245    <style>246        body {{247            font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif;248            background-color: #000000;249            color: #f5f5f7;250            padding: 40px;251        }}252        pre {{253            background: #1d1d1f;254            padding: 24px;255            border-radius: 12px;256            overflow-x: auto;257        }}258    </style>259</head>260<body>261    <h1>ComfyUI Workflow</h1>262    <p>Error: Invalid JSON format</p>263    <pre>{json_content}</pre>264</body>265</html>"""266    except Exception as e:267        print(f"Error prettifying ComfyUI JSON: {e}")268        return json_content269 270 271# Note: parse_transformers_js_output, parse_python_requirements, strip_tool_call_markers,272# remove_code_block, extract_import_statements, generate_requirements_txt_with_llm,273# and parse_multi_file_python_output are now imported from backend_parsers.py274 275 276def is_streamlit_code(code: str) -> bool:277    """Check if code is Streamlit"""278    return 'import streamlit' in code or 'streamlit.run' in code279 280 281def is_gradio_code(code: str) -> bool:282    """Check if code is Gradio or Daggr"""283    return 'import gradio' in code or 'gr.' in code or 'import daggr' in code or 'from daggr' in code284 285 286def detect_sdk_from_code(code: str, language: str) -> str:287    """Detect the appropriate SDK from code and language"""288    if language == "html":289        return "static"290    elif language == "transformers.js":291        return "static"292    elif language == "comfyui":293        return "static"294    elif language == "react":295        return "docker"296    elif language == "streamlit" or is_streamlit_code(code):297        return "docker"298    elif language == "gradio" or language == "daggr" or is_gradio_code(code):299        return "gradio"300    else:301        return "gradio"  # Default302 303 304def add_anycoder_tag_to_readme(api, repo_id: str, app_port: Optional[int] = None, sdk: Optional[str] = None) -> None:305    """306    Download existing README, add anycoder tag and app_port if needed, and upload back.307    Preserves all existing README content and frontmatter.308    309    Args:310        api: HuggingFace API client311        repo_id: Repository ID (username/space-name)312        app_port: Optional port number to set for Docker spaces (e.g., 7860)313        sdk: Optional SDK type (e.g., 'gradio', 'streamlit', 'docker', 'static')314    """315    try:316        import tempfile317        import re318        319        # Download the existing README320        readme_path = api.hf_hub_download(321            repo_id=repo_id,322            filename="README.md",323            repo_type="space"324        )325        326        # Read the existing README content327        with open(readme_path, 'r', encoding='utf-8') as f:328            content = f.read()329        330        # Parse frontmatter and content331        if content.startswith('---'):332            # Split frontmatter and body333            parts = content.split('---', 2)334            if len(parts) >= 3:335                frontmatter = parts[1].strip()336                body = parts[2] if len(parts) > 2 else ""337                338                # Check if tags already exist339                if 'tags:' in frontmatter:340                    # Add anycoder to existing tags if not present341                    if '- anycoder' not in frontmatter:342                        frontmatter = re.sub(r'(tags:\s*\n(?:\s*-\s*[^\n]+\n)*)', r'\1- anycoder\n', frontmatter)343                else:344                    # Add tags section with anycoder345                    frontmatter += '\ntags:\n- anycoder'346                347                # Add app_port if specified and not already present348                if app_port is not None and 'app_port:' not in frontmatter:349                    frontmatter += f'\napp_port: {app_port}'350                351                # For Gradio spaces, always set sdk_version to 6.0.2352                if sdk == 'gradio':353                    if 'sdk_version:' in frontmatter:354                        # Update existing sdk_version355                        frontmatter = re.sub(r'sdk_version:\s*[^\n]+', 'sdk_version: 6.0.2', frontmatter)356                        print(f"[README] Updated sdk_version to 6.0.2 for Gradio space")357                    else:358                        # Add sdk_version359                        frontmatter += '\nsdk_version: 6.0.2'360                        print(f"[README] Added sdk_version: 6.0.2 for Gradio space")361                362                # Reconstruct the README363                new_content = f"---\n{frontmatter}\n---{body}"364            else:365                # Malformed frontmatter, just add tags at the end of frontmatter366                new_content = content.replace('---', '---\ntags:\n- anycoder\n---', 1)367        else:368            # No frontmatter, add it at the beginning369            app_port_line = f'\napp_port: {app_port}' if app_port else ''370            sdk_version_line = '\nsdk_version: 6.0.2' if sdk == 'gradio' else ''371            new_content = f"---\ntags:\n- anycoder{app_port_line}{sdk_version_line}\n---\n\n{content}"372        373        # Upload the modified README374        with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding='utf-8') as f:375            f.write(new_content)376            temp_path = f.name377        378        api.upload_file(379            path_or_fileobj=temp_path,380            path_in_repo="README.md",381            repo_id=repo_id,382            repo_type="space"383        )384        385        os.unlink(temp_path)386        387    except Exception as e:388        print(f"Warning: Could not modify README.md to add anycoder tag: {e}")389 390 391def create_dockerfile_for_streamlit(space_name: str) -> str:392    """Create Dockerfile for Streamlit app"""393    return f"""FROM python:3.11-slim394 395WORKDIR /app396 397COPY requirements.txt .398RUN pip install --no-cache-dir -r requirements.txt399 400COPY . .401 402EXPOSE 7860403 404CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]405"""406 407 408def create_dockerfile_for_react(space_name: str) -> str:409    """Create Dockerfile for React app"""410    return f"""FROM node:18-slim411 412# Use existing node user413USER node414ENV HOME=/home/node415ENV PATH=/home/node/.local/bin:$PATH416 417WORKDIR /home/node/app418 419COPY --chown=node:node package*.json ./420RUN npm install421 422COPY --chown=node:node . .423RUN npm run build424 425EXPOSE 7860426 427CMD ["npm", "start", "--", "-p", "7860"]428"""429 430 431def extract_space_id_from_history(history: Optional[List], username: Optional[str] = None) -> Optional[str]:432    """433    Extract existing space ID from chat history (for updates after followups/imports)434    435    Args:436        history: Chat history (list of lists [[role, content], ...] or list of dicts)437        username: Current username (to verify ownership of imported spaces)438    439    Returns:440        Space ID (username/space-name) if found, None otherwise441    """442    if not history:443        return None444    445    import re446    existing_space = None447    448    # Look through history for previous deployments or imports449    for msg in history:450        # Handle both list format [[role, content], ...] and dict format [{'role': ..., 'content': ...}, ...]451        if isinstance(msg, list) and len(msg) >= 2:452            role = msg[0]453            content = msg[1]454        elif isinstance(msg, dict):455            role = msg.get('role', '')456            content = msg.get('content', '')457        else:458            continue459        460        # Check assistant messages for deployment confirmations461        if role == 'assistant':462            # Look for various deployment success patterns (case-insensitive)463            content_lower = content.lower()464            has_deployment_indicator = (465                "deployed" in content_lower or 466                "updated" in content_lower or467                "✅" in content  # Check mark often indicates deployment success468            )469            470            if has_deployment_indicator:471                # Look for space URL pattern472                match = re.search(r'huggingface\.co/spaces/([^/\s\)]+/[^/\s\)]+)', content)473                if match:474                    existing_space = match.group(1)475                    print(f"[Extract Space] Found existing space: {existing_space}")476                    break477        478        # Check user messages for imports479        elif role == 'user':480            if "import" in content.lower() and "space" in content.lower():481                # Extract space name from import message482                match = re.search(r'huggingface\.co/spaces/([^/\s\)]+/[^/\s\)]+)', content)483                if match:484                    imported_space = match.group(1)485                    # Only use imported space if user owns it (can update it)486                    if username and imported_space.startswith(f"{username}/"):487                        existing_space = imported_space488                        break489                    # If user doesn't own the imported space, we'll create a new one490                    # (existing_space remains None, triggering new deployment)491    492    return existing_space493 494 495def deploy_to_huggingface_space(496    code: str,497    language: str,498    space_name: Optional[str] = None,499    token: Optional[str] = None,500    username: Optional[str] = None,501    description: Optional[str] = None,502    private: bool = False,503    existing_repo_id: Optional[str] = None,504    commit_message: Optional[str] = None,505    history: Optional[List[Dict]] = None506) -> Tuple[bool, str, Optional[str]]:507    """508    Deploy code to HuggingFace Spaces (create new or update existing)509    510    Args:511        code: Generated code to deploy512        language: Target language/framework (html, gradio, streamlit, react, transformers.js, comfyui)513        space_name: Name for the space (auto-generated if None, ignored if existing_repo_id provided)514        token: HuggingFace API token515        username: HuggingFace username516        description: Space description517        private: Whether to make the space private (only for new spaces)518        existing_repo_id: If provided (username/space-name), updates this space instead of creating new one519        commit_message: Custom commit message (defaults to "Deploy from anycoder" or "Update from anycoder")520        history: Chat history (list of dicts with 'role' and 'content') - used to detect followups/imports521    522    Returns:523        Tuple of (success: bool, message: str, space_url: Optional[str])524    """525    if not token:526        token = os.getenv("HF_TOKEN")527        if not token:528            return False, "No HuggingFace token provided", None529    530    try:531        api = HfApi(token=token)532        533        # Get username if not provided (needed for history tracking)534        if not username:535            try:536                user_info = api.whoami()537                username = user_info.get("name") or user_info.get("preferred_username") or "user"538            except Exception as e:539                pass  # Will handle later if needed540        541        # Check history for existing space if not explicitly provided542        # This enables automatic updates for followup prompts and imported spaces543        if not existing_repo_id and history:544            existing_repo_id = extract_space_id_from_history(history, username)545            if existing_repo_id:546                print(f"[Deploy] Detected existing space from history: {existing_repo_id}")547        548        # Determine if this is an update or new deployment549        is_update = existing_repo_id is not None550        551        print(f"[Deploy] ========== DEPLOYMENT DECISION ==========")552        print(f"[Deploy] existing_repo_id provided: {existing_repo_id}")553        print(f"[Deploy] history provided: {history is not None} (length: {len(history) if history else 0})")554        print(f"[Deploy] username: {username}")555        print(f"[Deploy] is_update: {is_update}")556        print(f"[Deploy] language: {language}")557        print(f"[Deploy] ============================================")558        559        # For React space updates (followup changes), handle SEARCH/REPLACE blocks560        if is_update and language == "react":561            print(f"[Deploy] React space update - checking for search/replace blocks")562            563            # Import search/replace utilities564            from backend_search_replace import has_search_replace_blocks, parse_file_specific_changes, apply_search_replace_changes565            from huggingface_hub import hf_hub_download566            567            # Check if code contains search/replace blocks568            if has_search_replace_blocks(code):569                print(f"[Deploy] Detected SEARCH/REPLACE blocks - applying targeted changes")570                571                # Parse file-specific changes from code572                file_changes = parse_file_specific_changes(code)573                574                # Download existing files from the space575                try:576                    print(f"[Deploy] Downloading existing files from space: {existing_repo_id}")577                    578                    # Get list of files in the space579                    space_files = api.list_repo_files(repo_id=existing_repo_id, repo_type="space")580                    print(f"[Deploy] Found {len(space_files)} files in space: {space_files}")581                    582                    # Download relevant files (React/Next.js files)583                    react_file_patterns = ['.js', '.jsx', '.ts', '.tsx', '.css', '.json', 'Dockerfile']584                    existing_files = {}585                    586                    for file_path in space_files:587                        # Skip non-code files588                        if any(file_path.endswith(ext) or ext in file_path for ext in react_file_patterns):589                            try:590                                downloaded_path = hf_hub_download(591                                    repo_id=existing_repo_id,592                                    filename=file_path,593                                    repo_type="space",594                                    token=token595                                )596                                with open(downloaded_path, 'r', encoding='utf-8') as f:597                                    existing_files[file_path] = f.read()598                                print(f"[Deploy] Downloaded: {file_path} ({len(existing_files[file_path])} chars)")599                            except Exception as e:600                                print(f"[Deploy] Warning: Could not download {file_path}: {e}")601                    602                    if not existing_files:603                        print(f"[Deploy] Warning: No React files found in space, falling back to full deployment")604                    else:605                        # Apply search/replace changes to the appropriate files606                        updated_files = []607                        608                        # Check if changes are file-specific or global609                        if "__all__" in file_changes:610                            # Global changes - try to apply to all files611                            changes_text = file_changes["__all__"]612                            print(f"[Deploy] Applying global search/replace changes")613                            614                            # Try to apply to each file615                            for file_path, original_content in existing_files.items():616                                modified_content = apply_search_replace_changes(original_content, changes_text)617                                if modified_content != original_content:618                                    print(f"[Deploy] Modified {file_path}")619                                    success, msg = update_space_file(620                                        repo_id=existing_repo_id,621                                        file_path=file_path,622                                        content=modified_content,623                                        token=token,624                                        commit_message=commit_message or f"Update {file_path} from anycoder"625                                    )626                                    if success:627                                        updated_files.append(file_path)628                                    else:629                                        print(f"[Deploy] Warning: Failed to update {file_path}: {msg}")630                        else:631                            # File-specific changes632                            for filename, changes_text in file_changes.items():633                                # Find the file in existing files (handle both with/without directory prefix)634                                matching_file = None635                                for file_path in existing_files.keys():636                                    if file_path == filename or file_path.endswith('/' + filename):637                                        matching_file = file_path638                                        break639                                640                                if matching_file:641                                    original_content = existing_files[matching_file]642                                    modified_content = apply_search_replace_changes(original_content, changes_text)643                                    644                                    print(f"[Deploy] Applying changes to {matching_file}")645                                    success, msg = update_space_file(646                                        repo_id=existing_repo_id,647                                        file_path=matching_file,648                                        content=modified_content,649                                        token=token,650                                        commit_message=commit_message or f"Update {matching_file} from anycoder"651                                    )652                                    653                                    if success:654                                        updated_files.append(matching_file)655                                    else:656                                        print(f"[Deploy] Warning: Failed to update {matching_file}: {msg}")657                                else:658                                    print(f"[Deploy] Warning: File {filename} not found in space")659                        660                        if updated_files:661                            space_url = f"https://huggingface.co/spaces/{existing_repo_id}"662                            files_list = ", ".join(updated_files)663                            return True, f"✅ Updated {len(updated_files)} file(s): {files_list}! View at: {space_url}", space_url664                        else:665                            return False, "No files were updated", None666                    667                except Exception as e:668                    print(f"[Deploy] Error applying search/replace changes: {e}")669                    import traceback670                    traceback.print_exc()671                    # Fall through to normal deployment672            else:673                print(f"[Deploy] No SEARCH/REPLACE blocks detected, proceeding with full file update")674                # Fall through to normal React deployment below675        676        # For Gradio space updates (import/redesign), update .py files and upload all new files677        if is_update and language in ["gradio", "daggr"]:678            print(f"[Deploy] Gradio space update - updating .py files and uploading any new files")679            680            # Parse the code to get all files681            files = parse_multi_file_python_output(code)682            683            # Fallback if no files parsed684            if not files:685                print(f"[Deploy] No file markers found, using entire code as app.py")686                cleaned_code = remove_code_block(code)687                files['app.py'] = cleaned_code688            689            if not files:690                return False, "Error: No files found in generated code", None691            692            print(f"[Deploy] Generated {len(files)} file(s): {list(files.keys())}")693            694            # For redesign operations, ONLY update app.py to preserve other helper files695            # Detect redesign from commit message OR from history (user prompt contains "redesign")696            is_redesign = False697            if commit_message and "redesign" in commit_message.lower():698                is_redesign = True699            elif history:700                # Check last user message for "redesign" keyword701                for role, content in reversed(history):702                    if role == "user" and content and "redesign" in content.lower():703                        is_redesign = True704                        break705            706            if is_redesign:707                print(f"[Deploy] Redesign operation detected - filtering to ONLY app.py")708                app_py_content = files.get('app.py')709                if not app_py_content:710                    return False, "Error: No app.py found in redesign output", None711                files = {'app.py': app_py_content}712                print(f"[Deploy] Will only update app.py ({len(app_py_content)} chars)")713            714            # Upload all generated files (the LLM is instructed to only output .py files,715            # but if it creates new assets/data files, we should upload those too)716            # This approach updates .py files and adds any new files without touching717            # existing non-.py files that weren't generated718            updated_files = []719            for file_path, content in files.items():720                print(f"[Deploy] Uploading {file_path} ({len(content)} chars)")721                success, msg = update_space_file(722                    repo_id=existing_repo_id,723                    file_path=file_path,724                    content=content,725                    token=token,726                    commit_message=commit_message or f"Update {file_path} from anycoder"727                )728                729                if success:730                    updated_files.append(file_path)731                else:732                    print(f"[Deploy] Warning: Failed to update {file_path}: {msg}")733            734            if updated_files:735                space_url = f"https://huggingface.co/spaces/{existing_repo_id}"736                files_list = ", ".join(updated_files)737                return True, f"✅ Updated {len(updated_files)} file(s): {files_list}! View at: {space_url}", space_url738            else:739                return False, "Failed to update any files", None740        741        if is_update:742            # Use existing repo743            repo_id = existing_repo_id744            space_name = existing_repo_id.split('/')[-1]745            if '/' in existing_repo_id:746                username = existing_repo_id.split('/')[0]747            elif not username:748                # Get username if still not available749                try:750                    user_info = api.whoami()751                    username = user_info.get("name") or user_info.get("preferred_username") or "user"752                except Exception as e:753                    return False, f"Failed to get user info: {str(e)}", None754        else:755            # Get username if not provided756            if not username:757                try:758                    user_info = api.whoami()759                    username = user_info.get("name") or user_info.get("preferred_username") or "user"760                except Exception as e:761                    return False, f"Failed to get user info: {str(e)}", None762            763            # Generate space name if not provided or empty764            if not space_name or space_name.strip() == "":765                space_name = f"anycoder-{uuid.uuid4().hex[:8]}"766                print(f"[Deploy] Auto-generated space name: {space_name}")767            768            # Clean space name (no spaces, lowercase, alphanumeric + hyphens)769            space_name = re.sub(r'[^a-z0-9-]', '-', space_name.lower())770            space_name = re.sub(r'-+', '-', space_name).strip('-')771            772            # Ensure space_name is not empty after cleaning773            if not space_name:774                space_name = f"anycoder-{uuid.uuid4().hex[:8]}"775                print(f"[Deploy] Space name was empty after cleaning, regenerated: {space_name}")776            777            repo_id = f"{username}/{space_name}"778            print(f"[Deploy] Using repo_id: {repo_id}")779        780        # Detect SDK781        sdk = detect_sdk_from_code(code, language)782        783        # Create temporary directory for files784        with tempfile.TemporaryDirectory() as temp_dir:785            temp_path = Path(temp_dir)786            787            # Parse code based on language788            app_port = None  # Track if we need app_port for Docker spaces789            use_individual_uploads = False  # Flag for transformers.js790            791            if language == "transformers.js":792                try:793                    files = parse_transformers_js_output(code)794                    print(f"[Deploy] Parsed transformers.js files: {list(files.keys())}")795                    796                    # Log file sizes for debugging797                    for fname, fcontent in files.items():798                        if fcontent:799                            print(f"[Deploy] {fname}: {len(fcontent)} characters")800                        else:801                            print(f"[Deploy] {fname}: EMPTY")802                    803                    # Validate all three files are present in the dict804                    required_files = {'index.html', 'index.js', 'style.css'}805                    missing_from_dict = required_files - set(files.keys())806                    807                    if missing_from_dict:808                        error_msg = f"Failed to parse required files: {', '.join(sorted(missing_from_dict))}. "809                        error_msg += f"Parsed files: {', '.join(files.keys()) if files else 'none'}. "810                        error_msg += "Transformers.js apps require all three files (index.html, index.js, style.css). Please regenerate using the correct format."811                        print(f"[Deploy] {error_msg}")812                        return False, error_msg, None813                    814                    # Validate files have actual content (not empty or whitespace-only)815                    empty_files = [name for name in required_files if not files.get(name, '').strip()]816                    if empty_files:817                        error_msg = f"Empty file content detected: {', '.join(sorted(empty_files))}. "818                        error_msg += "All three files must contain actual code. Please regenerate with complete content."819                        print(f"[Deploy] {error_msg}")820                        return False, error_msg, None821                    822                    # Write transformers.js files to temp directory823                    for filename, content in files.items():824                        file_path = temp_path / filename825                        print(f"[Deploy] Writing {filename} ({len(content)} chars) to {file_path}")826                        # Use text mode - Python handles encoding automatically827                        if filename == "requirements.txt":828                            content = enforce_critical_versions(content)829                        file_path.write_text(content, encoding='utf-8')830                        # Verify the write was successful831                        written_size = file_path.stat().st_size832                        print(f"[Deploy] Verified {filename}: {written_size} bytes on disk")833                    834                    # For transformers.js, we'll upload files individually (not via upload_folder)835                    use_individual_uploads = True836                    837                except Exception as e:838                    print(f"[Deploy] Error parsing transformers.js: {e}")839                    import traceback840                    traceback.print_exc()841                    return False, f"Error parsing transformers.js output: {str(e)}", None842                843            elif language == "html":844                html_code = parse_html_code(code)845                (temp_path / "index.html").write_text(html_code, encoding='utf-8')846                847            elif language == "comfyui":848                # ComfyUI is JSON, wrap in stylized HTML viewer with download button849                html_code = prettify_comfyui_json_for_html(code)850                (temp_path / "index.html").write_text(html_code, encoding='utf-8')851                852            elif language in ["gradio", "streamlit", "daggr"]:853                files = parse_multi_file_python_output(code)854                855                # Fallback: if no files parsed (missing === markers), treat entire code as app.py856                if not files:857                    print(f"[Deploy] No file markers found in {language} code, using entire code as app.py")858                    # Clean up code blocks if present859                    cleaned_code = remove_code_block(code)860                    # Determine app filename based on language861                    app_filename = "streamlit_app.py" if language == "streamlit" else "app.py"862                    files[app_filename] = cleaned_code863                864                # Write Python files (create subdirectories if needed)865                for filename, content in files.items():866                    file_path = temp_path / filename867                    file_path.parent.mkdir(parents=True, exist_ok=True)868                    if filename == "requirements.txt":869                        content = enforce_critical_versions(content)870                    file_path.write_text(content, encoding='utf-8')871                872                # Ensure requirements.txt exists - generate from imports if missing873                if "requirements.txt" not in files:874                    # Get the main app file (app.py for gradio, streamlit_app.py or app.py for streamlit)875                    main_app = files.get('streamlit_app.py') or files.get('app.py', '')876                    if main_app:877                        print(f"[Deploy] Generating requirements.txt from imports in {language} app")878                        import_statements = extract_import_statements(main_app)879                        requirements_content = generate_requirements_txt_with_llm(import_statements)880                        (temp_path / "requirements.txt").write_text(requirements_content, encoding='utf-8')881                        print(f"[Deploy] Generated requirements.txt with {len(requirements_content.splitlines())} lines")882                    else:883                        # Fallback to minimal requirements if no app file found884                        if language == "gradio":885                            (temp_path / "requirements.txt").write_text("gradio>=4.0.0\n", encoding='utf-8')886                        elif language == "streamlit":887                            (temp_path / "requirements.txt").write_text("streamlit>=1.30.0\n", encoding='utf-8')888                        elif language == "daggr":889                            (temp_path / "requirements.txt").write_text("daggr>=0.5.4\ngradio>=6.0.2\n", encoding='utf-8')890                891                # Create Dockerfile if needed892                if sdk == "docker":893                    if language == "streamlit":894                        dockerfile = create_dockerfile_for_streamlit(space_name)895                        (temp_path / "Dockerfile").write_text(dockerfile, encoding='utf-8')896                    app_port = 7860  # Set app_port for Docker spaces897                    use_individual_uploads = True  # Streamlit uses individual file uploads898                899            elif language == "react":900                # Parse React output to get all files (uses === filename === markers)901                files = parse_react_output(code)902                903                if not files:904                    return False, "Error: Could not parse React output", None905                906                # If Dockerfile is missing, use template907                if 'Dockerfile' not in files:908                    dockerfile = create_dockerfile_for_react(space_name)909                    files['Dockerfile'] = dockerfile910                911                # Write all React files (create subdirectories if needed)912                for filename, content in files.items():913                    file_path = temp_path / filename914                    file_path.parent.mkdir(parents=True, exist_ok=True)915                    file_path.write_text(content, encoding='utf-8')916                917                app_port = 7860  # Set app_port for Docker spaces918                use_individual_uploads = True  # React uses individual file uploads919            920            else:921                # Default: treat as Gradio app922                files = parse_multi_file_python_output(code)923                924                # Fallback: if no files parsed (missing === markers), treat entire code as app.py925                if not files:926                    print(f"[Deploy] No file markers found in default (gradio) code, using entire code as app.py")927                    # Clean up code blocks if present928                    cleaned_code = remove_code_block(code)929                    files['app.py'] = cleaned_code930                931                # Write files (create subdirectories if needed)932                for filename, content in files.items():933                    file_path = temp_path / filename934                    file_path.parent.mkdir(parents=True, exist_ok=True)935                    if filename == "requirements.txt":936                        content = enforce_critical_versions(content)937                    file_path.write_text(content, encoding='utf-8')938                939                # Generate requirements.txt from imports if missing940                if "requirements.txt" not in files:941                    main_app = files.get('app.py', '')942                    if main_app:943                        print(f"[Deploy] Generating requirements.txt from imports in default app")944                        import_statements = extract_import_statements(main_app)945                        requirements_content = generate_requirements_txt_with_llm(import_statements)946                        (temp_path / "requirements.txt").write_text(requirements_content, encoding='utf-8')947                        print(f"[Deploy] Generated requirements.txt with {len(requirements_content.splitlines())} lines")948                    else:949                        # Fallback to minimal requirements if no app file found950                        if language == "daggr":951                            (temp_path / "requirements.txt").write_text("daggr>=0.5.4\ngradio>=6.0.2\n", encoding='utf-8')952                        else:953                            (temp_path / "requirements.txt").write_text("gradio>=4.0.0\n", encoding='utf-8')954            955            # Don't create README - HuggingFace will auto-generate it956            # We'll add the anycoder tag after deployment957            958            # ONLY create repo for NEW deployments of non-Docker, non-transformers.js spaces959            # Docker and transformers.js handle repo creation separately below960            # This matches the Gradio version logic (line 1256 in ui.py)961            if not is_update and sdk != "docker" and language not in ["transformers.js"]:962                print(f"[Deploy] Creating NEW {sdk} space: {repo_id}")963                try:964                    api.create_repo(965                        repo_id=repo_id,966                        repo_type="space",967                        space_sdk=sdk,968                        private=private,969                        exist_ok=True970                    )971                except Exception as e:972                    return False, f"Failed to create space: {str(e)}", None973            elif is_update:974                print(f"[Deploy] UPDATING existing space: {repo_id} (skipping create_repo)")975            976            # Handle transformers.js spaces (create repo via duplicate_space)977            if language == "transformers.js":978                if not is_update:979                    print(f"[Deploy] Creating NEW transformers.js space via template duplication")980                    print(f"[Deploy] space_name value: '{space_name}' (type: {type(space_name)})")981                    982                    # Safety check for space_name983                    if not space_name:984                        return False, "Internal error: space_name is None after generation", None985                    986                    try:987                        from huggingface_hub import duplicate_space988                        989                        # duplicate_space expects just the space name (not full repo_id)990                        # Use strip() to clean the space name991                        clean_space_name = space_name.strip()992                        print(f"[Deploy] Attempting to duplicate template space to: {clean_space_name}")993                        994                        duplicated_repo = duplicate_space(995                            from_id="static-templates/transformers.js",996                            to_id=clean_space_name,997                            token=token,998                            exist_ok=True999                        )1000                        print(f"[Deploy] Template duplication result: {duplicated_repo} (type: {type(duplicated_repo)})")1001                    except Exception as e:1002                        print(f"[Deploy] Exception during duplicate_space: {type(e).__name__}: {str(e)}")1003                        1004                        # Check if space actually exists (success despite error)1005                        space_exists = False1006                        try:1007                            if api.space_info(repo_id):1008                                space_exists = True1009                        except:1010                            pass1011 1012                        # Handle RepoUrl object "errors"1013                        error_msg = str(e)1014                        if ("'url'" in error_msg or "RepoUrl" in error_msg) and space_exists:1015                            print(f"[Deploy] Space exists despite RepoUrl error, continuing with deployment")1016                        else:1017                            # Fallback to regular create_repo1018                            print(f"[Deploy] Template duplication failed, attempting fallback to create_repo: {e}")1019                            try:1020                                api.create_repo(1021                                    repo_id=repo_id,1022                                    repo_type="space",1023                                    space_sdk="static",1024                                    private=private,1025                                    exist_ok=True1026                                )1027                                print(f"[Deploy] Fallback create_repo successful")1028                            except Exception as e2:1029                                return False, f"Failed to create transformers.js space (both duplication and fallback failed): {str(e2)}", None1030                else:1031                    # For updates, verify we can access the existing space1032                    try:1033                        space_info = api.space_info(repo_id)1034                        if not space_info:1035                            return False, f"Could not access space {repo_id} for update", None1036                    except Exception as e:1037                        return False, f"Cannot update space {repo_id}: {str(e)}", None1038            1039            # Handle Docker spaces (React/Streamlit) - create repo separately1040            elif sdk == "docker" and language in ["streamlit", "react"]:1041                if not is_update:1042                    print(f"[Deploy] Creating NEW Docker space for {language}: {repo_id}")1043                    try:1044                        from huggingface_hub import create_repo as hf_create_repo1045                        hf_create_repo(1046                            repo_id=repo_id,1047                            repo_type="space",1048                            space_sdk="docker",1049                            token=token,1050                            exist_ok=True1051                        )1052                    except Exception as e:1053                        return False, f"Failed to create Docker space: {str(e)}", None1054            1055            # Upload files1056            if not commit_message:1057                commit_message = "Update from anycoder" if is_update else "Deploy from anycoder"1058            1059            try:1060                if language == "transformers.js":1061                    # Special handling for transformers.js - create NEW temp files for each upload1062                    # This matches the working pattern in ui.py1063                    import time1064                    1065                    # Get the parsed files from earlier1066                    files_to_upload = [1067                        ("index.html", files.get('index.html')),1068                        ("index.js", files.get('index.js')),1069                        ("style.css", files.get('style.css'))1070                    ]1071                    1072                    max_attempts = 31073                    for file_name, file_content in files_to_upload:1074                        if not file_content:1075                            return False, f"Missing content for {file_name}", None1076                        1077                        success = False1078                        last_error = None1079                        1080                        for attempt in range(max_attempts):1081                            temp_file_path = None1082                            try:1083                                # Create a NEW temp file for this upload (matches Gradio version approach)1084                                print(f"[Deploy] Creating temp file for {file_name} with {len(file_content)} chars")1085                                # Use text mode "w" - lets Python handle encoding automatically (better emoji support)1086                                with tempfile.NamedTemporaryFile("w", suffix=f".{file_name.split('.')[-1]}", delete=False) as f:1087                                    f.write(file_content)1088                                    temp_file_path = f.name1089                                # File is now closed and flushed, safe to upload1090                                1091                                # Upload the file without commit_message (HF handles this for spaces)1092                                api.upload_file(1093                                    path_or_fileobj=temp_file_path,1094                                    path_in_repo=file_name,1095                                    repo_id=repo_id,1096                                    repo_type="space"1097                                )1098                                success = True1099                                print(f"[Deploy] Successfully uploaded {file_name}")1100                                break1101                                1102                            except Exception as e:1103                                last_error = e1104                                error_str = str(e)1105                                print(f"[Deploy] Upload error for {file_name}: {error_str}")1106                                if "403" in error_str or "Forbidden" in error_str:1107                                    return False, f"Permission denied uploading {file_name}. Check your token has write access to {repo_id}.", None1108                                1109                                if attempt < max_attempts - 1:1110                                    time.sleep(2)  # Wait before retry1111                                    print(f"[Deploy] Retry {attempt + 1}/{max_attempts} for {file_name}")1112                            finally:1113                                # Clean up temp file1114                                if temp_file_path and os.path.exists(temp_file_path):1115                                    os.unlink(temp_file_path)1116                        1117                        if not success:1118                            return False, f"Failed to upload {file_name} after {max_attempts} attempts: {last_error}", None1119                1120                elif use_individual_uploads:1121                    # For React, Streamlit: upload each file individually1122                    import time1123                    1124                    # Get list of files to upload from temp directory1125                    files_to_upload = []1126                    for file_path in temp_path.rglob('*'):1127                        if file_path.is_file():1128                            # Get relative path from temp directory (use forward slashes for repo paths)1129                            rel_path = file_path.relative_to(temp_path)1130                            files_to_upload.append(str(rel_path).replace('\\', '/'))1131                    1132                    if not files_to_upload:1133                        return False, "No files to upload", None1134                    1135                    print(f"[Deploy] Uploading {len(files_to_upload)} files individually: {files_to_upload}")1136                    1137                    max_attempts = 31138                    for filename in files_to_upload:1139                        # Convert back to Path for filesystem operations1140                        file_path = temp_path / filename.replace('/', os.sep)1141                        if not file_path.exists():1142                            return False, f"Failed to upload: {filename} not found", None1143                        1144                        # Upload with retry logic1145                        success = False1146                        last_error = None1147                        1148                        for attempt in range(max_attempts):1149                            try:1150                                # Upload without commit_message - HF API handles this for spaces1151                                api.upload_file(1152                                    path_or_fileobj=str(file_path),1153                                    path_in_repo=filename,1154                                    repo_id=repo_id,1155                                    repo_type="space"1156                                )1157                                success = True1158                                print(f"[Deploy] Successfully uploaded {filename}")1159                                break1160                            except Exception as e:1161                                last_error = e1162                                error_str = str(e)1163                                print(f"[Deploy] Upload error for {filename}: {error_str}")1164                                if "403" in error_str or "Forbidden" in error_str:1165                                    return False, f"Permission denied uploading {filename}. Check your token has write access to {repo_id}.", None1166                                if attempt < max_attempts - 1:1167                                    time.sleep(2)  # Wait before retry1168                                    print(f"[Deploy] Retry {attempt + 1}/{max_attempts} for {filename}")1169                        1170                        if not success:1171                            return False, f"Failed to upload {filename} after {max_attempts} attempts: {last_error}", None1172                else:1173                    # For other languages, use upload_folder1174                    print(f"[Deploy] Uploading folder to {repo_id}")1175                    api.upload_folder(1176                        folder_path=str(temp_path),1177                        repo_id=repo_id,1178                        repo_type="space"1179                    )1180            except Exception as e:1181                return False, f"Failed to upload files: {str(e)}", None1182            1183            # After successful upload, modify the auto-generated README to add anycoder tag1184            # For new spaces: HF auto-generates README, wait and modify it1185            # For updates: README should already exist, just add tag if missing1186            try:1187                import time1188                if not is_update:1189                    time.sleep(2)  # Give HF time to generate README for new spaces1190                add_anycoder_tag_to_readme(api, repo_id, app_port, sdk)1191            except Exception as e:1192                # Don't fail deployment if README modification fails1193                print(f"Warning: Could not add anycoder tag to README: {e}")1194            1195            # For transformers.js updates, trigger a space restart to ensure changes take effect1196            if is_update and language == "transformers.js":1197                try:1198                    api.restart_space(repo_id=repo_id)1199                    print(f"[Deploy] Restarted space after update: {repo_id}")1200                except Exception as restart_error:

Showing the first 1,200 of 1704 lines. Download the file for the rest.