akhaliq HF Staff commited on
Commit
0babdff
·
1 Parent(s): 857cdba
Files changed (1) hide show
  1. backend_deploy.py +1 -392
backend_deploy.py CHANGED
@@ -26,28 +26,6 @@ from backend_parsers import (
26
  )
27
 
28
 
29
- def parse_html_code(code: str) -> str:
30
- """Extract HTML code from various formats"""
31
- code = code.strip()
32
-
33
- # If already clean HTML, return as-is
34
- if code.startswith('<!DOCTYPE') or code.startswith('<html'):
35
- return code
36
-
37
- # Try to extract from code blocks
38
- if '```html' in code:
39
- match = re.search(r'```html\s*(.*?)\s*```', code, re.DOTALL)
40
- if match:
41
- return match.group(1).strip()
42
-
43
- if '```' in code:
44
- match = re.search(r'```\s*(.*?)\s*```', code, re.DOTALL)
45
- if match:
46
- return match.group(1).strip()
47
-
48
- return code
49
-
50
-
51
  def prettify_comfyui_json_for_html(json_content: str) -> str:
52
  """Convert ComfyUI JSON to stylized HTML display with download button"""
53
  try:
@@ -293,375 +271,6 @@ def prettify_comfyui_json_for_html(json_content: str) -> str:
293
  # and parse_multi_file_python_output are now imported from backend_parsers.py
294
 
295
 
296
- def is_streamlit_code(code: str) -> bool:
297
- """Check if code is Streamlit"""
298
- return 'import streamlit' in code or 'streamlit.run' in code
299
-
300
-
301
- def is_gradio_code(code: str) -> bool:
302
- """Check if code is Gradio"""
303
- return 'import gradio' in code or 'gr.' in code
304
-
305
-
306
- def detect_sdk_from_code(code: str, language: str) -> str:
307
- """Detect the appropriate SDK from code and language"""
308
- if language == "html":
309
- return "static"
310
- elif language == "transformers.js":
311
- return "static"
312
- elif language == "comfyui":
313
- return "static"
314
- elif language == "react":
315
- return "docker"
316
- elif language == "streamlit" or is_streamlit_code(code):
317
- return "docker"
318
- elif language == "gradio" or is_gradio_code(code):
319
- return "gradio"
320
- else:
321
- return "gradio" # Default
322
-
323
-
324
- def add_anycoder_tag_to_readme(api, repo_id: str, app_port: Optional[int] = None) -> None:
325
- """
326
- Download existing README, add anycoder tag and app_port if needed, and upload back.
327
- Preserves all existing README content and frontmatter.
328
-
329
- Args:
330
- api: HuggingFace API client
331
-
332
- css_patterns = [
333
- r'```css\s*\n([\s\S]*?)(?:```|\Z)',
334
- r'```\s*(?:style\.css|css)\s*\n([\s\S]*?)(?:```|\Z)'
335
- ]
336
-
337
- # Extract HTML content
338
- for pattern in html_patterns:
339
- html_match = re.search(pattern, code, re.IGNORECASE)
340
- if html_match:
341
- files['index.html'] = html_match.group(1).strip()
342
- break
343
-
344
- # Extract JavaScript content
345
- for pattern in js_patterns:
346
- js_match = re.search(pattern, code, re.IGNORECASE)
347
- if js_match:
348
- files['index.js'] = js_match.group(1).strip()
349
- break
350
-
351
- # Extract CSS content
352
- for pattern in css_patterns:
353
- css_match = re.search(pattern, code, re.IGNORECASE)
354
- if css_match:
355
- files['style.css'] = css_match.group(1).strip()
356
- break
357
-
358
- # Fallback: support === index.html === format if any file is missing
359
- if not (files['index.html'] and files['index.js'] and files['style.css']):
360
- # Use regex to extract sections
361
- html_fallback = re.search(r'===\s*index\.html\s*===\s*\n([\s\S]+?)(?=\n===|$)', code, re.IGNORECASE)
362
- js_fallback = re.search(r'===\s*index\.js\s*===\s*\n([\s\S]+?)(?=\n===|$)', code, re.IGNORECASE)
363
- css_fallback = re.search(r'===\s*style\.css\s*===\s*\n([\s\S]+?)(?=\n===|$)', code, re.IGNORECASE)
364
-
365
- if html_fallback:
366
- files['index.html'] = html_fallback.group(1).strip()
367
- if js_fallback:
368
- files['index.js'] = js_fallback.group(1).strip()
369
- if css_fallback:
370
- files['style.css'] = css_fallback.group(1).strip()
371
-
372
- # Additional fallback: extract from numbered sections or file headers
373
- if not (files['index.html'] and files['index.js'] and files['style.css']):
374
- # Try patterns like "1. index.html:" or "**index.html**"
375
- patterns = [
376
- (r'(?:^\d+\.\s*|^##\s*|^\*\*\s*)index\.html(?:\s*:|\*\*:?)\s*\n([\s\S]+?)(?=\n(?:\d+\.|##|\*\*|===)|$)', 'index.html'),
377
- (r'(?:^\d+\.\s*|^##\s*|^\*\*\s*)index\.js(?:\s*:|\*\*:?)\s*\n([\s\S]+?)(?=\n(?:\d+\.|##|\*\*|===)|$)', 'index.js'),
378
- (r'(?:^\d+\.\s*|^##\s*|^\*\*\s*)style\.css(?:\s*:|\*\*:?)\s*\n([\s\S]+?)(?=\n(?:\d+\.|##|\*\*|===)|$)', 'style.css')
379
- ]
380
-
381
- for pattern, file_key in patterns:
382
- if not files[file_key]:
383
- match = re.search(pattern, code, re.IGNORECASE | re.MULTILINE)
384
- if match:
385
- # Clean up the content by removing any code block markers
386
- content = match.group(1).strip()
387
- content = re.sub(r'^```\w*\s*\n', '', content)
388
- content = re.sub(r'\n```\s*$', '', content)
389
- files[file_key] = content.strip()
390
-
391
- return files
392
-
393
-
394
- def parse_python_requirements(code: str) -> Optional[str]:
395
- """Extract requirements.txt content from code if present"""
396
- # Look for requirements.txt section
397
- req_pattern = r'===\s*requirements\.txt\s*===\s*(.*?)(?====|$)'
398
- match = re.search(req_pattern, code, re.DOTALL | re.IGNORECASE)
399
-
400
- if match:
401
- requirements = match.group(1).strip()
402
- # Clean up code blocks
403
- requirements = re.sub(r'^```\w*\s*', '', requirements, flags=re.MULTILINE)
404
- requirements = re.sub(r'```\s*$', '', requirements, flags=re.MULTILINE)
405
- return requirements
406
-
407
- return None
408
-
409
-
410
- def strip_tool_call_markers(text):
411
- """Remove TOOL_CALL markers and thinking tags that some LLMs add to their output."""
412
- if not text:
413
- return text
414
- # Remove [TOOL_CALL] and [/TOOL_CALL] markers
415
- text = re.sub(r'\[/?TOOL_CALL\]', '', text, flags=re.IGNORECASE)
416
- # Remove <think> and </think> tags and their content
417
- text = re.sub(r'<think>[\s\S]*?</think>', '', text, flags=re.IGNORECASE)
418
- # Remove any remaining unclosed <think> tags at the start
419
- text = re.sub(r'^<think>[\s\S]*?(?=\n|$)', '', text, flags=re.IGNORECASE | re.MULTILINE)
420
- # Remove any remaining </think> tags
421
- text = re.sub(r'</think>', '', text, flags=re.IGNORECASE)
422
- # Remove standalone }} that appears with tool calls
423
- # Only remove if it's on its own line or at the end
424
- text = re.sub(r'^\s*\}\}\s*$', '', text, flags=re.MULTILINE)
425
- return text.strip()
426
-
427
-
428
- def remove_code_block(text):
429
- """Remove code block markers from text."""
430
- # First strip any tool call markers
431
- text = strip_tool_call_markers(text)
432
-
433
- # Try to match code blocks with language markers
434
- patterns = [
435
- r'```(?:html|HTML)\n([\s\S]+?)\n```', # Match ```html or ```HTML
436
- r'```\n([\s\S]+?)\n```', # Match code blocks without language markers
437
- r'```([\s\S]+?)```' # Match code blocks without line breaks
438
- ]
439
- for pattern in patterns:
440
- match = re.search(pattern, text, re.DOTALL)
441
- if match:
442
- extracted = match.group(1).strip()
443
- # Remove a leading language marker line (e.g., 'python') if present
444
- if extracted.split('\n', 1)[0].strip().lower() in ['python', 'html', 'css', 'javascript', 'json', 'c', 'cpp', 'markdown', 'latex', 'jinja2', 'typescript', 'yaml', 'dockerfile', 'shell', 'r', 'sql']:
445
- return extracted.split('\n', 1)[1] if '\n' in extracted else ''
446
- return extracted
447
- # If no code block is found, return as-is
448
- return text.strip()
449
-
450
-
451
- def extract_import_statements(code):
452
- """Extract import statements from generated code."""
453
- import_statements = []
454
-
455
- # Built-in Python modules to exclude
456
- builtin_modules = {
457
- 'os', 'sys', 'json', 'time', 'datetime', 'random', 'math', 're', 'collections',
458
- 'itertools', 'functools', 'pathlib', 'urllib', 'http', 'email', 'html', 'xml',
459
- 'csv', 'tempfile', 'shutil', 'subprocess', 'threading', 'multiprocessing',
460
- 'asyncio', 'logging', 'typing', 'base64', 'hashlib', 'secrets', 'uuid',
461
- 'copy', 'pickle', 'io', 'contextlib', 'warnings', 'sqlite3', 'gzip', 'zipfile',
462
- 'tarfile', 'socket', 'ssl', 'platform', 'getpass', 'pwd', 'grp', 'stat',
463
- 'glob', 'fnmatch', 'linecache', 'traceback', 'inspect', 'keyword', 'token',
464
- 'tokenize', 'ast', 'code', 'codeop', 'dis', 'py_compile', 'compileall',
465
- 'importlib', 'pkgutil', 'modulefinder', 'runpy', 'site', 'sysconfig'
466
- }
467
-
468
- try:
469
- # Try to parse as Python AST
470
- tree = ast.parse(code)
471
-
472
- for node in ast.walk(tree):
473
- if isinstance(node, ast.Import):
474
- for alias in node.names:
475
- module_name = alias.name.split('.')[0]
476
- if module_name not in builtin_modules and not module_name.startswith('_'):
477
- import_statements.append(f"import {alias.name}")
478
-
479
- elif isinstance(node, ast.ImportFrom):
480
- if node.module:
481
- module_name = node.module.split('.')[0]
482
- if module_name not in builtin_modules and not module_name.startswith('_'):
483
- names = [alias.name for alias in node.names]
484
- import_statements.append(f"from {node.module} import {', '.join(names)}")
485
-
486
- except SyntaxError:
487
- # Fallback: use regex to find import statements
488
- for line in code.split('\n'):
489
- line = line.strip()
490
- if line.startswith('import ') or line.startswith('from '):
491
- # Check if it's not a builtin module
492
- if line.startswith('import '):
493
- module_name = line.split()[1].split('.')[0]
494
- elif line.startswith('from '):
495
- module_name = line.split()[1].split('.')[0]
496
-
497
- if module_name not in builtin_modules and not module_name.startswith('_'):
498
- import_statements.append(line)
499
-
500
- return list(set(import_statements)) # Remove duplicates
501
-
502
-
503
- def generate_requirements_txt_with_llm(import_statements):
504
- """Generate requirements.txt content using LLM based on import statements."""
505
- if not import_statements:
506
- return "# No additional dependencies required\n"
507
-
508
- # Use a lightweight model for this task
509
- try:
510
- client = get_inference_client("zai-org/GLM-4.6", "auto")
511
- actual_model_id = get_real_model_id("zai-org/GLM-4.6")
512
-
513
- imports_text = '\n'.join(import_statements)
514
-
515
- prompt = f"""Based on the following Python import statements, generate a comprehensive requirements.txt file with all necessary and commonly used related packages:
516
-
517
- {imports_text}
518
-
519
- Instructions:
520
- - Include the direct packages needed for the imports
521
- - Include commonly used companion packages and dependencies for better functionality
522
- - Use correct PyPI package names (e.g., PIL -> Pillow, sklearn -> scikit-learn)
523
- - IMPORTANT: For diffusers, ALWAYS use: git+https://github.com/huggingface/diffusers
524
- - IMPORTANT: For transformers, ALWAYS use: git+https://github.com/huggingface/transformers
525
- - IMPORTANT: If diffusers is installed, also include transformers and sentencepiece as they usually go together
526
- - Examples of comprehensive dependencies:
527
- * diffusers often needs: git+https://github.com/huggingface/transformers, sentencepiece, accelerate, torch, tokenizers
528
- * transformers often needs: accelerate, torch, tokenizers, datasets
529
- * gradio often needs: requests, Pillow for image handling
530
- * pandas often needs: numpy, openpyxl for Excel files
531
- * matplotlib often needs: numpy, pillow for image saving
532
- * sklearn often needs: numpy, scipy, joblib
533
- * streamlit often needs: pandas, numpy, requests
534
- * opencv-python often needs: numpy, pillow
535
- * fastapi often needs: uvicorn, pydantic
536
- * torch often needs: torchvision, torchaudio (if doing computer vision/audio)
537
- - Include packages for common file formats if relevant (openpyxl, python-docx, PyPDF2)
538
- - Do not include Python built-in modules
539
- - Do not specify versions unless there are known compatibility issues
540
- - One package per line
541
- - If no external packages are needed, return "# No additional dependencies required"
542
-
543
- 🚨 CRITICAL OUTPUT FORMAT:
544
- - Output ONLY the package names, one per line (plain text format)
545
- - Do NOT use markdown formatting (no ```, no bold, no headings, no lists)
546
- - Do NOT add any explanatory text before or after the package list
547
- - Do NOT wrap the output in code blocks
548
- - Just output raw package names as they would appear in requirements.txt
549
-
550
- Generate a comprehensive requirements.txt that ensures the application will work smoothly:"""
551
-
552
- messages = [
553
- {"role": "system", "content": "You are a Python packaging expert specializing in creating comprehensive, production-ready requirements.txt files. Output ONLY plain text package names without any markdown formatting, code blocks, or explanatory text. Your goal is to ensure applications work smoothly by including not just direct dependencies but also commonly needed companion packages, popular extensions, and supporting libraries that developers typically need together."},
554
- {"role": "user", "content": prompt}
555
- ]
556
-
557
- response = client.chat.completions.create(
558
- model=actual_model_id,
559
- messages=messages,
560
- max_tokens=1024,
561
- temperature=0.1
562
- )
563
-
564
- requirements_content = response.choices[0].message.content.strip()
565
-
566
- # Clean up the response in case it includes extra formatting
567
- if '```' in requirements_content:
568
- requirements_content = remove_code_block(requirements_content)
569
-
570
- # Enhanced cleanup for markdown and formatting
571
- lines = requirements_content.split('\n')
572
- clean_lines = []
573
- for line in lines:
574
- stripped_line = line.strip()
575
-
576
- # Skip lines that are markdown formatting
577
- if (stripped_line == '```' or
578
- stripped_line.startswith('```') or
579
- stripped_line.startswith('#') and not stripped_line.startswith('# ') or # Skip markdown headers but keep comments
580
- stripped_line.startswith('**') or # Skip bold text
581
- stripped_line.startswith('*') and not stripped_line[1:2].isalnum() or # Skip markdown lists but keep package names starting with *
582
- stripped_line.startswith('-') and not stripped_line[1:2].isalnum() or # Skip markdown lists but keep package names starting with -
583
- stripped_line.startswith('===') or # Skip section dividers
584
- stripped_line.startswith('---') or # Skip horizontal rules
585
- stripped_line.lower().startswith('here') or # Skip explanatory text
586
- stripped_line.lower().startswith('this') or # Skip explanatory text
587
- stripped_line.lower().startswith('the') or # Skip explanatory text
588
- stripped_line.lower().startswith('based on') or # Skip explanatory text
589
- stripped_line == ''): # Skip empty lines unless they're at natural boundaries
590
- continue
591
-
592
- # Keep lines that look like valid package specifications
593
- # Valid lines: package names, git+https://, comments starting with "# "
594
- if (stripped_line.startswith('# ') or # Valid comments
595
- stripped_line.startswith('git+') or # Git dependencies
596
- stripped_line[0].isalnum() or # Package names start with alphanumeric
597
- '==' in stripped_line or # Version specifications
598
- '>=' in stripped_line or # Version specifications
599
- '<=' in stripped_line): # Version specifications
600
- clean_lines.append(line)
601
-
602
- requirements_content = '\n'.join(clean_lines).strip()
603
-
604
- # Ensure it ends with a newline
605
- if requirements_content and not requirements_content.endswith('\n'):
606
- requirements_content += '\n'
607
-
608
- return requirements_content if requirements_content else "# No additional dependencies required\n"
609
-
610
- except Exception as e:
611
- # Fallback: simple extraction with basic mapping
612
- print(f"[Deploy] Warning: LLM requirements generation failed: {e}, using fallback")
613
- dependencies = set()
614
- special_cases = {
615
- 'PIL': 'Pillow',
616
- 'sklearn': 'scikit-learn',
617
- 'skimage': 'scikit-image',
618
- 'bs4': 'beautifulsoup4'
619
- }
620
-
621
- for stmt in import_statements:
622
- if stmt.startswith('import '):
623
- module_name = stmt.split()[1].split('.')[0]
624
- package_name = special_cases.get(module_name, module_name)
625
- dependencies.add(package_name)
626
- elif stmt.startswith('from '):
627
- module_name = stmt.split()[1].split('.')[0]
628
- package_name = special_cases.get(module_name, module_name)
629
- dependencies.add(package_name)
630
-
631
- if dependencies:
632
- return '\n'.join(sorted(dependencies)) + '\n'
633
- else:
634
- return "# No additional dependencies required\n"
635
-
636
-
637
- def parse_multi_file_python_output(code: str) -> Dict[str, str]:
638
- """Parse multi-file Python output (e.g., Gradio, Streamlit)"""
639
- files = {}
640
-
641
- # Pattern to match file sections
642
- pattern = r'===\s*(\S+\.(?:py|txt))\s*===\s*(.*?)(?====|$)'
643
- matches = re.finditer(pattern, code, re.DOTALL | re.IGNORECASE)
644
-
645
- for match in matches:
646
- filename = match.group(1).strip()
647
- content = match.group(2).strip()
648
-
649
- # Clean up code blocks if present
650
- content = re.sub(r'^```\w*\s*', '', content, flags=re.MULTILINE)
651
- content = re.sub(r'```\s*$', '', content, flags=re.MULTILINE)
652
-
653
- files[filename] = content
654
-
655
- # If no files were parsed, treat as single app.py
656
- if not files:
657
- # Clean up code blocks
658
- clean_code = re.sub(r'^```\w*\s*', '', code, flags=re.MULTILINE)
659
- clean_code = re.sub(r'```\s*$', '', clean_code, flags=re.MULTILINE)
660
- files['app.py'] = clean_code.strip()
661
-
662
- return files
663
-
664
-
665
  def is_streamlit_code(code: str) -> bool:
666
  """Check if code is Streamlit"""
667
  return 'import streamlit' in code or 'streamlit.run' in code
@@ -916,7 +525,7 @@ def deploy_to_huggingface_space(
916
  print(f"[Deploy] {error_msg}")
917
  return False, error_msg, None
918
 
919
- # Write transformers.js files to temp directory
920
  for filename, content in files.items():
921
  file_path = temp_path / filename
922
  print(f"[Deploy] Writing {filename} ({len(content)} chars) to {file_path}")
 
26
  )
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def prettify_comfyui_json_for_html(json_content: str) -> str:
30
  """Convert ComfyUI JSON to stylized HTML display with download button"""
31
  try:
 
271
  # and parse_multi_file_python_output are now imported from backend_parsers.py
272
 
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  def is_streamlit_code(code: str) -> bool:
275
  """Check if code is Streamlit"""
276
  return 'import streamlit' in code or 'streamlit.run' in code
 
525
  print(f"[Deploy] {error_msg}")
526
  return False, error_msg, None
527
 
528
+ # Write transformers.js files to temp directory
529
  for filename, content in files.items():
530
  file_path = temp_path / filename
531
  print(f"[Deploy] Writing {filename} ({len(content)} chars) to {file_path}")