Upload folder using huggingface_hub
Browse files- code/config/constants.py +11 -0
- code/huggingface/dockerfile_gen.py +626 -0
- code/huggingface/push.py +45 -12
- code/server/routes.py +21 -8
- index.html +35 -3
code/config/constants.py
CHANGED
|
@@ -109,6 +109,17 @@ For single-file code, use standard markdown fenced blocks:
|
|
| 109 |
```typescript for TypeScript
|
| 110 |
etc.
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
When generating web apps with HTML/CSS/JS, return a single self-contained HTML document with all CSS and JavaScript inline. Make the page fully responsive: html/body at margin:0 and 100% width/height, use flexbox/grid layouts, and size any canvas to its container.
|
| 113 |
|
| 114 |
When generating Gradio apps, create a complete app.py with:
|
|
|
|
| 109 |
```typescript for TypeScript
|
| 110 |
etc.
|
| 111 |
|
| 112 |
+
JAVASCRIPT / TYPESCRIPT PROJECTS:
|
| 113 |
+
For React, Next.js, Vue.js, Express, NestJS, or any JS/TS framework:
|
| 114 |
+
- ALWAYS use the @@FILE: multi-file format
|
| 115 |
+
- Include a package.json with name, version, scripts, and dependencies
|
| 116 |
+
- Include all source files (src/App.jsx, src/index.js, etc.)
|
| 117 |
+
- For React+Vite: include vite.config.js and index.html
|
| 118 |
+
- For Next.js: include next.config.js with output: 'standalone'
|
| 119 |
+
- For Express: main entry is index.js with app.listen(7860)
|
| 120 |
+
- Server ports MUST be 7860 and bind to 0.0.0.0
|
| 121 |
+
- Do NOT include node_modules or lock files
|
| 122 |
+
|
| 123 |
When generating web apps with HTML/CSS/JS, return a single self-contained HTML document with all CSS and JavaScript inline. Make the page fully responsive: html/body at margin:0 and 100% width/height, use flexbox/grid layouts, and size any canvas to its container.
|
| 124 |
|
| 125 |
When generating Gradio apps, create a complete app.py with:
|
code/huggingface/dockerfile_gen.py
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Dockerfile and package.json generator for JS/TS frameworks.
|
| 2 |
+
|
| 3 |
+
Auto-generates Dockerfile, package.json, and .dockerignore for
|
| 4 |
+
React, Next.js, Vue.js, Express, NestJS, and plain Node.js projects
|
| 5 |
+
so they can be pushed to HuggingFace Docker Spaces.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import re
|
| 11 |
+
from typing import Any
|
| 12 |
+
|
| 13 |
+
# βββ Framework Detection ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
+
|
| 15 |
+
# Keywords in code that identify a framework
|
| 16 |
+
FRAMEWORK_SIGNALS: dict[str, list[str]] = {
|
| 17 |
+
"nextjs": [
|
| 18 |
+
"from 'next", 'from "next',
|
| 19 |
+
"next/link", "next/image", "next/router", "next/head",
|
| 20 |
+
"NextResponse", "NextRequest", "next/navigation",
|
| 21 |
+
"getServerSideProps", "getStaticProps",
|
| 22 |
+
],
|
| 23 |
+
"react": [
|
| 24 |
+
"from 'react", 'from "react',
|
| 25 |
+
"react-dom", "ReactDOM", "useState", "useEffect",
|
| 26 |
+
"jsx", "tsx", "React.Component", "React.createElement",
|
| 27 |
+
],
|
| 28 |
+
"vue": [
|
| 29 |
+
"from 'vue", 'from "vue',
|
| 30 |
+
"createApp", "Vue.createApp", "<template>",
|
| 31 |
+
"defineComponent", "ref(", "reactive(",
|
| 32 |
+
],
|
| 33 |
+
"express": [
|
| 34 |
+
"require('express')", 'require("express")',
|
| 35 |
+
"from 'express", 'from "express',
|
| 36 |
+
"express()", "express.Router",
|
| 37 |
+
],
|
| 38 |
+
"nestjs": [
|
| 39 |
+
"@Module", "@Controller", "@Get", "@Post", "@Put", "@Delete",
|
| 40 |
+
"from '@nestjs", 'from "@nestjs',
|
| 41 |
+
"NestFactory.create",
|
| 42 |
+
],
|
| 43 |
+
"nodejs": [
|
| 44 |
+
"require('http')", "http.createServer",
|
| 45 |
+
"const http = require", "import http from",
|
| 46 |
+
],
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def detect_framework(files: dict[str, str]) -> str:
|
| 51 |
+
"""Detect the JS/TS framework from project files.
|
| 52 |
+
|
| 53 |
+
Returns one of: 'nextjs', 'react', 'vue', 'express', 'nestjs', 'nodejs', 'static'
|
| 54 |
+
"""
|
| 55 |
+
all_code = "\n".join(files.values())
|
| 56 |
+
|
| 57 |
+
# Check file names first (strong signal)
|
| 58 |
+
has_next_config = any(
|
| 59 |
+
f.startswith("next.config") for f in files
|
| 60 |
+
)
|
| 61 |
+
if has_next_config:
|
| 62 |
+
return "nextjs"
|
| 63 |
+
|
| 64 |
+
# Check package.json if present
|
| 65 |
+
for fname, content in files.items():
|
| 66 |
+
if fname == "package.json" or fname.endswith("/package.json"):
|
| 67 |
+
try:
|
| 68 |
+
import json
|
| 69 |
+
pkg = json.loads(content)
|
| 70 |
+
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
| 71 |
+
if "next" in deps:
|
| 72 |
+
return "nextjs"
|
| 73 |
+
if "vue" in deps or "@vue/cli-service" in deps:
|
| 74 |
+
return "vue"
|
| 75 |
+
if "@nestjs/core" in deps:
|
| 76 |
+
return "nestjs"
|
| 77 |
+
if "express" in deps:
|
| 78 |
+
return "express"
|
| 79 |
+
if "react" in deps or "react-dom" in deps:
|
| 80 |
+
return "react"
|
| 81 |
+
except Exception:
|
| 82 |
+
pass
|
| 83 |
+
|
| 84 |
+
# Check code content for framework signals
|
| 85 |
+
for fw, signals in FRAMEWORK_SIGNALS.items():
|
| 86 |
+
for signal in signals:
|
| 87 |
+
if signal in all_code:
|
| 88 |
+
return fw
|
| 89 |
+
|
| 90 |
+
# Check file extensions
|
| 91 |
+
has_jsx_tsx = any(f.endswith((".jsx", ".tsx")) for f in files)
|
| 92 |
+
has_vue = any(f.endswith(".vue") for f in files)
|
| 93 |
+
|
| 94 |
+
if has_vue:
|
| 95 |
+
return "vue"
|
| 96 |
+
if has_jsx_tsx:
|
| 97 |
+
return "react"
|
| 98 |
+
|
| 99 |
+
return "static"
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def is_js_project(files: dict[str, str]) -> bool:
|
| 103 |
+
"""Check if the project is a JavaScript/TypeScript project."""
|
| 104 |
+
js_extensions = {".js", ".jsx", ".ts", ".tsx", ".vue", ".mjs", ".cjs"}
|
| 105 |
+
has_package_json = any("package.json" in f for f in files)
|
| 106 |
+
has_js_files = any(
|
| 107 |
+
any(f.endswith(ext) for ext in js_extensions)
|
| 108 |
+
for f in files
|
| 109 |
+
)
|
| 110 |
+
return has_package_json or has_js_files
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# βββ Dockerfile Templates βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 114 |
+
|
| 115 |
+
def _dockerfile_nextjs() -> str:
|
| 116 |
+
"""Dockerfile for Next.js projects."""
|
| 117 |
+
return """FROM node:20-slim AS base
|
| 118 |
+
|
| 119 |
+
# Install dependencies only when needed
|
| 120 |
+
FROM base AS deps
|
| 121 |
+
WORKDIR /app
|
| 122 |
+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
| 123 |
+
RUN \\
|
| 124 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
|
| 125 |
+
elif [ -f package-lock.json ]; then npm ci; \\
|
| 126 |
+
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
|
| 127 |
+
else npm i; \\
|
| 128 |
+
fi
|
| 129 |
+
|
| 130 |
+
# Rebuild source code only when needed
|
| 131 |
+
FROM base AS builder
|
| 132 |
+
WORKDIR /app
|
| 133 |
+
COPY --from=deps /app/node_modules ./node_modules
|
| 134 |
+
COPY . .
|
| 135 |
+
RUN npm run build
|
| 136 |
+
|
| 137 |
+
# Production image
|
| 138 |
+
FROM base AS runner
|
| 139 |
+
WORKDIR /app
|
| 140 |
+
ENV NODE_ENV=production
|
| 141 |
+
RUN addgroup --system --gid 1001 nodejs
|
| 142 |
+
RUN adduser --system --uid 1001 nextjs
|
| 143 |
+
COPY --from=builder /app/public ./public
|
| 144 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
| 145 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
| 146 |
+
USER nextjs
|
| 147 |
+
EXPOSE 3000
|
| 148 |
+
ENV PORT=3000
|
| 149 |
+
ENV HOSTNAME="0.0.0.0"
|
| 150 |
+
CMD ["node", "server.js"]
|
| 151 |
+
"""
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _dockerfile_react() -> str:
|
| 155 |
+
"""Dockerfile for React (Vite/CRA) projects β served with nginx."""
|
| 156 |
+
return """FROM node:20-slim AS build
|
| 157 |
+
WORKDIR /app
|
| 158 |
+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
| 159 |
+
RUN \\
|
| 160 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
|
| 161 |
+
elif [ -f package-lock.json ]; then npm ci; \\
|
| 162 |
+
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
|
| 163 |
+
else npm i; \\
|
| 164 |
+
fi
|
| 165 |
+
COPY . .
|
| 166 |
+
RUN npm run build
|
| 167 |
+
|
| 168 |
+
FROM nginx:alpine
|
| 169 |
+
COPY --from=build /app/build /usr/share/nginx/html
|
| 170 |
+
COPY --from=build /app/dist /usr/share/nginx/html 2>/dev/null || true
|
| 171 |
+
RUN cat > /etc/nginx/conf.d/default.conf << 'EOF'
|
| 172 |
+
server {
|
| 173 |
+
listen 7860;
|
| 174 |
+
server_name localhost;
|
| 175 |
+
root /usr/share/nginx/html;
|
| 176 |
+
index index.html;
|
| 177 |
+
location / {
|
| 178 |
+
try_files $uri $uri/ /index.html;
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
EOF
|
| 182 |
+
EXPOSE 7860
|
| 183 |
+
CMD ["nginx", "-g", "daemon off;"]
|
| 184 |
+
"""
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _dockerfile_vue() -> str:
|
| 188 |
+
"""Dockerfile for Vue.js projects β served with nginx."""
|
| 189 |
+
return """FROM node:20-slim AS build
|
| 190 |
+
WORKDIR /app
|
| 191 |
+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
| 192 |
+
RUN \\
|
| 193 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
|
| 194 |
+
elif [ -f package-lock.json ]; then npm ci; \\
|
| 195 |
+
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
|
| 196 |
+
else npm i; \\
|
| 197 |
+
fi
|
| 198 |
+
COPY . .
|
| 199 |
+
RUN npm run build
|
| 200 |
+
|
| 201 |
+
FROM nginx:alpine
|
| 202 |
+
COPY --from=build /app/dist /usr/share/nginx/html
|
| 203 |
+
RUN cat > /etc/nginx/conf.d/default.conf << 'EOF'
|
| 204 |
+
server {
|
| 205 |
+
listen 7860;
|
| 206 |
+
server_name localhost;
|
| 207 |
+
root /usr/share/nginx/html;
|
| 208 |
+
index index.html;
|
| 209 |
+
location / {
|
| 210 |
+
try_files $uri $uri/ /index.html;
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
EOF
|
| 214 |
+
EXPOSE 7860
|
| 215 |
+
CMD ["nginx", "-g", "daemon off;"]
|
| 216 |
+
"""
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _dockerfile_express() -> str:
|
| 220 |
+
"""Dockerfile for Express/Node.js server projects."""
|
| 221 |
+
return """FROM node:20-slim
|
| 222 |
+
WORKDIR /app
|
| 223 |
+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
| 224 |
+
RUN \\
|
| 225 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
|
| 226 |
+
elif [ -f package-lock.json ]; then npm ci; \\
|
| 227 |
+
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
|
| 228 |
+
else npm i; \\
|
| 229 |
+
fi
|
| 230 |
+
COPY . .
|
| 231 |
+
RUN addgroup --system --gid 1001 appuser && adduser --system --uid 1001 appuser
|
| 232 |
+
USER appuser
|
| 233 |
+
EXPOSE 7860
|
| 234 |
+
ENV PORT=7860
|
| 235 |
+
ENV HOST=0.0.0.0
|
| 236 |
+
CMD ["node", "index.js"]
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def _dockerfile_nestjs() -> str:
|
| 241 |
+
"""Dockerfile for NestJS projects."""
|
| 242 |
+
return """FROM node:20-slim AS build
|
| 243 |
+
WORKDIR /app
|
| 244 |
+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
| 245 |
+
RUN \\
|
| 246 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \\
|
| 247 |
+
elif [ -f package-lock.json ]; then npm ci; \\
|
| 248 |
+
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \\
|
| 249 |
+
else npm i; \\
|
| 250 |
+
fi
|
| 251 |
+
COPY . .
|
| 252 |
+
RUN npm run build
|
| 253 |
+
|
| 254 |
+
FROM node:20-slim
|
| 255 |
+
WORKDIR /app
|
| 256 |
+
COPY package.json package-lock.json* yarn.lock* pnpm-lock.yaml* ./
|
| 257 |
+
RUN \\
|
| 258 |
+
if [ -f yarn.lock ]; then yarn --frozen-lockfile --production; \\
|
| 259 |
+
elif [ -f package-lock.json ]; then npm ci --only=production; \\
|
| 260 |
+
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile --prod; \\
|
| 261 |
+
else npm i --only=production; \\
|
| 262 |
+
fi
|
| 263 |
+
COPY --from=build /app/dist ./dist
|
| 264 |
+
RUN addgroup --system --gid 1001 appuser && adduser --system --uid 1001 appuser
|
| 265 |
+
USER appuser
|
| 266 |
+
EXPOSE 7860
|
| 267 |
+
ENV PORT=7860
|
| 268 |
+
CMD ["node", "dist/main.js"]
|
| 269 |
+
"""
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def generate_dockerfile(framework: str) -> str:
|
| 273 |
+
"""Generate a Dockerfile for the given framework.
|
| 274 |
+
|
| 275 |
+
Args:
|
| 276 |
+
framework: One of 'nextjs', 'react', 'vue', 'express', 'nestjs', 'nodejs', 'static'
|
| 277 |
+
|
| 278 |
+
Returns:
|
| 279 |
+
Dockerfile content as string.
|
| 280 |
+
"""
|
| 281 |
+
templates = {
|
| 282 |
+
"nextjs": _dockerfile_nextjs,
|
| 283 |
+
"react": _dockerfile_react,
|
| 284 |
+
"vue": _dockerfile_vue,
|
| 285 |
+
"express": _dockerfile_express,
|
| 286 |
+
"nestjs": _dockerfile_nestjs,
|
| 287 |
+
"nodejs": _dockerfile_express, # Same as express for plain Node
|
| 288 |
+
}
|
| 289 |
+
gen = templates.get(framework)
|
| 290 |
+
if gen:
|
| 291 |
+
return gen()
|
| 292 |
+
# Fallback: generic node server
|
| 293 |
+
return _dockerfile_express()
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
# βββ package.json Generator βββββββββββββββββββββββββββββββββββββββββββββ
|
| 297 |
+
|
| 298 |
+
def _scan_js_imports(code: str) -> set[str]:
|
| 299 |
+
"""Scan JS/TS code for import/require statements and return package names."""
|
| 300 |
+
packages = set()
|
| 301 |
+
|
| 302 |
+
# ESM: import xxx from 'pkg' / import 'pkg'
|
| 303 |
+
for m in re.finditer(r"import\s+.*?\s+from\s+['\"](@?[\w-]+/[\w-]+|[\w-]+)", code):
|
| 304 |
+
packages.add(m.group(1))
|
| 305 |
+
|
| 306 |
+
# ESM: import 'pkg'
|
| 307 |
+
for m in re.finditer(r"import\s+['\"](@?[\w-]+/[\w-]+|[\w-]+)['\"]", code):
|
| 308 |
+
packages.add(m.group(1))
|
| 309 |
+
|
| 310 |
+
# CJS: require('pkg')
|
| 311 |
+
for m in re.finditer(r"require\s*\(\s*['\"](@?[\w-]+/[\w-]+|[\w-]+)['\"]\s*\)", code):
|
| 312 |
+
packages.add(m.group(1))
|
| 313 |
+
|
| 314 |
+
return packages
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
# Known packages that should NOT go in dependencies (built-in or types)
|
| 318 |
+
_SKIP_PACKAGES = {
|
| 319 |
+
"react", "react-dom", "next", "vue", "express",
|
| 320 |
+
"path", "fs", "http", "https", "url", "os", "crypto",
|
| 321 |
+
"stream", "util", "events", "buffer", "child_process",
|
| 322 |
+
"net", "tls", "zlib", "assert", "querystring",
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
# Framework-specific dependency sets
|
| 326 |
+
_FRAMEWORK_DEPS: dict[str, dict[str, str]] = {
|
| 327 |
+
"nextjs": {
|
| 328 |
+
"next": "14.2.0",
|
| 329 |
+
"react": "^18.3.0",
|
| 330 |
+
"react-dom": "^18.3.0",
|
| 331 |
+
},
|
| 332 |
+
"react": {
|
| 333 |
+
"react": "^18.3.0",
|
| 334 |
+
"react-dom": "^18.3.0",
|
| 335 |
+
"react-scripts": "5.0.1",
|
| 336 |
+
},
|
| 337 |
+
"vue": {
|
| 338 |
+
"vue": "^3.4.0",
|
| 339 |
+
},
|
| 340 |
+
"express": {
|
| 341 |
+
"express": "^4.19.0",
|
| 342 |
+
},
|
| 343 |
+
"nestjs": {
|
| 344 |
+
"@nestjs/core": "^10.3.0",
|
| 345 |
+
"@nestjs/common": "^10.3.0",
|
| 346 |
+
"@nestjs/platform-express": "^10.3.0",
|
| 347 |
+
"reflect-metadata": "^0.2.0",
|
| 348 |
+
"rxjs": "^7.8.0",
|
| 349 |
+
},
|
| 350 |
+
"nodejs": {},
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
# Common package version mapping
|
| 354 |
+
_PACKAGE_VERSIONS: dict[str, str] = {
|
| 355 |
+
"axios": "^1.6.0",
|
| 356 |
+
"lodash": "^4.17.21",
|
| 357 |
+
"cors": "^2.8.5",
|
| 358 |
+
"dotenv": "^16.4.0",
|
| 359 |
+
"mongoose": "^8.2.0",
|
| 360 |
+
"prisma": "^5.10.0",
|
| 361 |
+
"@prisma/client": "^5.10.0",
|
| 362 |
+
"zod": "^3.22.0",
|
| 363 |
+
"socket.io": "^4.7.0",
|
| 364 |
+
"multer": "^1.4.4",
|
| 365 |
+
"cookie-parser": "^1.4.6",
|
| 366 |
+
"express-session": "^1.18.0",
|
| 367 |
+
"jsonwebtoken": "^9.0.0",
|
| 368 |
+
"bcrypt": "^5.1.0",
|
| 369 |
+
"uuid": "^9.0.0",
|
| 370 |
+
"dayjs": "^1.11.10",
|
| 371 |
+
"chart.js": "^4.4.0",
|
| 372 |
+
"framer-motion": "^11.0.0",
|
| 373 |
+
"lucide-react": "^0.350.0",
|
| 374 |
+
"tailwindcss": "^3.4.0",
|
| 375 |
+
"postcss": "^8.4.0",
|
| 376 |
+
"autoprefixer": "^10.4.0",
|
| 377 |
+
"@vitejs/plugin-react": "^4.2.0",
|
| 378 |
+
"vite": "^5.1.0",
|
| 379 |
+
"typescript": "^5.3.0",
|
| 380 |
+
"@types/react": "^18.3.0",
|
| 381 |
+
"@types/react-dom": "^18.3.0",
|
| 382 |
+
"@types/node": "^20.11.0",
|
| 383 |
+
"tailwind-merge": "^2.2.0",
|
| 384 |
+
"clsx": "^2.1.0",
|
| 385 |
+
"class-variance-authority": "^0.7.0",
|
| 386 |
+
"@radix-ui/react-slot": "^1.0.2",
|
| 387 |
+
"next-themes": "^0.3.0",
|
| 388 |
+
"recharts": "^2.12.0",
|
| 389 |
+
"react-hook-form": "^7.50.0",
|
| 390 |
+
"@hookform/resolvers": "^3.3.0",
|
| 391 |
+
"zustand": "^4.5.0",
|
| 392 |
+
"jotai": "^2.6.0",
|
| 393 |
+
"tanstack": "^5.24.0",
|
| 394 |
+
"@tanstack/react-query": "^5.24.0",
|
| 395 |
+
"swr": "^2.2.0",
|
| 396 |
+
"nodemon": "^3.1.0",
|
| 397 |
+
"ts-node": "^10.9.0",
|
| 398 |
+
"ts-node-dev": "^2.0.0",
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def generate_package_json(
|
| 403 |
+
framework: str,
|
| 404 |
+
project_name: str = "my-app",
|
| 405 |
+
extra_deps: set[str] | None = None,
|
| 406 |
+
existing_content: str | None = None,
|
| 407 |
+
) -> str:
|
| 408 |
+
"""Generate a package.json for the given framework.
|
| 409 |
+
|
| 410 |
+
If existing_content is provided, merges dependencies into it.
|
| 411 |
+
"""
|
| 412 |
+
import json
|
| 413 |
+
|
| 414 |
+
# Start with existing or fresh
|
| 415 |
+
if existing_content:
|
| 416 |
+
try:
|
| 417 |
+
pkg = json.loads(existing_content)
|
| 418 |
+
except Exception:
|
| 419 |
+
pkg = {}
|
| 420 |
+
else:
|
| 421 |
+
pkg = {}
|
| 422 |
+
|
| 423 |
+
pkg.setdefault("name", project_name)
|
| 424 |
+
pkg.setdefault("version", "1.0.0")
|
| 425 |
+
pkg.setdefault("private", True)
|
| 426 |
+
|
| 427 |
+
deps = pkg.get("dependencies", {})
|
| 428 |
+
dev_deps = pkg.get("devDependencies", {})
|
| 429 |
+
|
| 430 |
+
# Add framework core deps
|
| 431 |
+
fw_deps = _FRAMEWORK_DEPS.get(framework, {})
|
| 432 |
+
for name, version in fw_deps.items():
|
| 433 |
+
deps[name] = version
|
| 434 |
+
|
| 435 |
+
# Add scanned extra deps
|
| 436 |
+
if extra_deps:
|
| 437 |
+
for dep in extra_deps:
|
| 438 |
+
if dep in _SKIP_PACKAGES:
|
| 439 |
+
continue
|
| 440 |
+
if dep in deps or dep in dev_deps:
|
| 441 |
+
continue
|
| 442 |
+
version = _PACKAGE_VERSIONS.get(dep, "^1.0.0")
|
| 443 |
+
# Dev deps
|
| 444 |
+
if dep.startswith("@types/") or dep in {"typescript", "nodemon", "ts-node", "ts-node-dev"}:
|
| 445 |
+
dev_deps[dep] = version
|
| 446 |
+
else:
|
| 447 |
+
deps[dep] = version
|
| 448 |
+
|
| 449 |
+
pkg["dependencies"] = deps
|
| 450 |
+
if dev_deps:
|
| 451 |
+
pkg["devDependencies"] = dev_deps
|
| 452 |
+
|
| 453 |
+
# Add scripts based on framework
|
| 454 |
+
scripts = pkg.get("scripts", {})
|
| 455 |
+
if framework == "nextjs":
|
| 456 |
+
scripts.setdefault("dev", "next dev")
|
| 457 |
+
scripts.setdefault("build", "next build")
|
| 458 |
+
scripts.setdefault("start", "next start -p 7860")
|
| 459 |
+
elif framework in ("react",):
|
| 460 |
+
scripts.setdefault("dev", "vite")
|
| 461 |
+
scripts.setdefault("build", "vite build")
|
| 462 |
+
scripts.setdefault("start", "vite preview --port 7860 --host 0.0.0.0")
|
| 463 |
+
# Ensure vite is in devDeps
|
| 464 |
+
if "vite" not in dev_deps and "vite" not in deps:
|
| 465 |
+
dev_deps["vite"] = "^5.1.0"
|
| 466 |
+
if "@vitejs/plugin-react" not in dev_deps:
|
| 467 |
+
dev_deps["@vitejs/plugin-react"] = "^4.2.0"
|
| 468 |
+
elif framework == "vue":
|
| 469 |
+
scripts.setdefault("dev", "vite")
|
| 470 |
+
scripts.setdefault("build", "vite build")
|
| 471 |
+
scripts.setdefault("start", "vite preview --port 7860 --host 0.0.0.0")
|
| 472 |
+
if "vite" not in dev_deps and "vite" not in deps:
|
| 473 |
+
dev_deps["vite"] = "^5.1.0"
|
| 474 |
+
elif framework in ("express", "nodejs"):
|
| 475 |
+
scripts.setdefault("dev", "node index.js")
|
| 476 |
+
scripts.setdefault("start", "node index.js")
|
| 477 |
+
elif framework == "nestjs":
|
| 478 |
+
scripts.setdefault("build", "nest build")
|
| 479 |
+
scripts.setdefault("start", "node dist/main.js")
|
| 480 |
+
|
| 481 |
+
pkg["scripts"] = scripts
|
| 482 |
+
pkg["dependencies"] = deps
|
| 483 |
+
if dev_deps:
|
| 484 |
+
pkg["devDependencies"] = dev_deps
|
| 485 |
+
|
| 486 |
+
return json.dumps(pkg, indent=2) + "\n"
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
# βββ .dockerignore ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 490 |
+
|
| 491 |
+
DOCKERIGNORE = """node_modules
|
| 492 |
+
npm-debug.log*
|
| 493 |
+
yarn-debug.log*
|
| 494 |
+
yarn-error.log*
|
| 495 |
+
.next
|
| 496 |
+
.git
|
| 497 |
+
.gitignore
|
| 498 |
+
README.md
|
| 499 |
+
.env
|
| 500 |
+
.env.local
|
| 501 |
+
.env.production
|
| 502 |
+
.DS_Store
|
| 503 |
+
"""
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
# βββ Vite Config Generators βββββββββββββββββββββββββββββββββββββββββββββ
|
| 507 |
+
|
| 508 |
+
def generate_vite_config(framework: str) -> str | None:
|
| 509 |
+
"""Generate a vite.config.js/ts if needed for React or Vue."""
|
| 510 |
+
if framework == "react":
|
| 511 |
+
return """import { defineConfig } from 'vite'
|
| 512 |
+
import react from '@vitejs/plugin-react'
|
| 513 |
+
|
| 514 |
+
export default defineConfig({
|
| 515 |
+
plugins: [react()],
|
| 516 |
+
server: {
|
| 517 |
+
host: '0.0.0.0',
|
| 518 |
+
port: 7860,
|
| 519 |
+
},
|
| 520 |
+
})
|
| 521 |
+
"""
|
| 522 |
+
if framework == "vue":
|
| 523 |
+
return """import { defineConfig } from 'vite'
|
| 524 |
+
import vue from '@vitejs/plugin-vue'
|
| 525 |
+
|
| 526 |
+
export default defineConfig({
|
| 527 |
+
plugins: [vue()],
|
| 528 |
+
server: {
|
| 529 |
+
host: '0.0.0.0',
|
| 530 |
+
port: 7860,
|
| 531 |
+
},
|
| 532 |
+
})
|
| 533 |
+
"""
|
| 534 |
+
return None
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
# βββ Next.js Config ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 538 |
+
|
| 539 |
+
def generate_next_config() -> str:
|
| 540 |
+
"""Generate next.config.js with standalone output for Docker."""
|
| 541 |
+
return """/** @type {import('next').NextConfig} */
|
| 542 |
+
const nextConfig = {
|
| 543 |
+
output: 'standalone',
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
module.exports = nextConfig
|
| 547 |
+
"""
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
# βββ Public index.html for Vite βββββββββββββββββββββββββββββββββββββββββ
|
| 551 |
+
|
| 552 |
+
def generate_index_html(title: str = "App") -> str:
|
| 553 |
+
"""Generate a minimal index.html for Vite projects."""
|
| 554 |
+
return f"""<!DOCTYPE html>
|
| 555 |
+
<html lang="en">
|
| 556 |
+
<head>
|
| 557 |
+
<meta charset="UTF-8" />
|
| 558 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 559 |
+
<title>{title}</title>
|
| 560 |
+
</head>
|
| 561 |
+
<body>
|
| 562 |
+
<div id="root"></div>
|
| 563 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 564 |
+
</body>
|
| 565 |
+
</html>
|
| 566 |
+
"""
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
# βββ Full Project Scaffold ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 570 |
+
|
| 571 |
+
def scaffold_js_project(
|
| 572 |
+
files: dict[str, str],
|
| 573 |
+
framework: str,
|
| 574 |
+
project_name: str = "my-app",
|
| 575 |
+
) -> dict[str, str]:
|
| 576 |
+
"""Add Dockerfile, package.json, and config files to a JS project.
|
| 577 |
+
|
| 578 |
+
Takes existing generated files and returns an augmented dict with
|
| 579 |
+
Docker support files added.
|
| 580 |
+
"""
|
| 581 |
+
augmented = dict(files)
|
| 582 |
+
|
| 583 |
+
# Detect imports from all JS/TS files
|
| 584 |
+
all_js_code = "\n".join(
|
| 585 |
+
content for fname, content in files.items()
|
| 586 |
+
if fname.endswith((".js", ".jsx", ".ts", ".tsx", ".vue", ".mjs"))
|
| 587 |
+
)
|
| 588 |
+
extra_deps = _scan_js_imports(all_js_code)
|
| 589 |
+
|
| 590 |
+
# Add Dockerfile
|
| 591 |
+
if "Dockerfile" not in augmented:
|
| 592 |
+
augmented["Dockerfile"] = generate_dockerfile(framework)
|
| 593 |
+
|
| 594 |
+
# Add .dockerignore
|
| 595 |
+
if ".dockerignore" not in augmented:
|
| 596 |
+
augmented[".dockerignore"] = DOCKERIGNORE
|
| 597 |
+
|
| 598 |
+
# Add or merge package.json
|
| 599 |
+
if "package.json" in augmented:
|
| 600 |
+
augmented["package.json"] = generate_package_json(
|
| 601 |
+
framework=framework,
|
| 602 |
+
project_name=project_name,
|
| 603 |
+
extra_deps=extra_deps,
|
| 604 |
+
existing_content=augmented["package.json"],
|
| 605 |
+
)
|
| 606 |
+
else:
|
| 607 |
+
augmented["package.json"] = generate_package_json(
|
| 608 |
+
framework=framework,
|
| 609 |
+
project_name=project_name,
|
| 610 |
+
extra_deps=extra_deps,
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
# Framework-specific config files
|
| 614 |
+
if framework == "nextjs" and "next.config.js" not in augmented and "next.config.mjs" not in augmented:
|
| 615 |
+
augmented["next.config.js"] = generate_next_config()
|
| 616 |
+
|
| 617 |
+
if framework in ("react", "vue"):
|
| 618 |
+
vite_cfg = generate_vite_config(framework)
|
| 619 |
+
if vite_cfg and "vite.config.js" not in augmented and "vite.config.ts" not in augmented:
|
| 620 |
+
augmented["vite.config.js"] = vite_cfg
|
| 621 |
+
|
| 622 |
+
# Add index.html entry point for Vite if not present
|
| 623 |
+
if "index.html" not in augmented:
|
| 624 |
+
augmented["index.html"] = generate_index_html(project_name)
|
| 625 |
+
|
| 626 |
+
return augmented
|
code/huggingface/push.py
CHANGED
|
@@ -15,6 +15,11 @@ from pathlib import Path
|
|
| 15 |
from typing import Any
|
| 16 |
|
| 17 |
from code.config.constants import MODEL_ID
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
|
@@ -117,12 +122,18 @@ def _find_entry_point(files: dict[str, str]) -> str:
|
|
| 117 |
|
| 118 |
Looks for app.py, main.py, or any Python file with a launcher pattern.
|
| 119 |
"""
|
| 120 |
-
# Priority order for entry points
|
| 121 |
candidates = ["app.py", "main.py", "index.py", "server.py", "run.py"]
|
| 122 |
for c in candidates:
|
| 123 |
if c in files:
|
| 124 |
return c
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
# Look for any .py file with if __name__ == "__main__" or .launch()
|
| 127 |
for fname, content in files.items():
|
| 128 |
if fname.endswith(".py"):
|
|
@@ -146,6 +157,11 @@ def _detect_sdk(files: dict[str, str], entry: str) -> str:
|
|
| 146 |
return "streamlit"
|
| 147 |
if "import gradio" in all_code or "from gradio" in all_code:
|
| 148 |
return "gradio"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
if any(f.endswith(".html") for f in files):
|
| 150 |
return "static"
|
| 151 |
if entry.endswith(".py"):
|
|
@@ -180,7 +196,7 @@ def push_to_huggingface(
|
|
| 180 |
"""Push generated project to HuggingFace Hub.
|
| 181 |
|
| 182 |
Creates the repo if it doesn't exist, writes all files,
|
| 183 |
-
and adds README.md and requirements.txt as needed.
|
| 184 |
"""
|
| 185 |
try:
|
| 186 |
from huggingface_hub import HfApi, create_repo
|
|
@@ -199,10 +215,24 @@ def push_to_huggingface(
|
|
| 199 |
entry_point = _find_entry_point(files)
|
| 200 |
detected_sdk = _detect_sdk(files, entry_point)
|
| 201 |
|
| 202 |
-
# Use detected SDK if user left it as "static" but project
|
| 203 |
if space_sdk == "static" and detected_sdk != "static":
|
| 204 |
space_sdk = detected_sdk
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
try:
|
| 207 |
if is_space:
|
| 208 |
create_repo(
|
|
@@ -229,8 +259,8 @@ def push_to_huggingface(
|
|
| 229 |
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
| 230 |
Path(full_path).write_text(content, encoding="utf-8")
|
| 231 |
|
| 232 |
-
# Ensure the entry point is named app.py for HF Spaces
|
| 233 |
-
if entry_point != "app.py" and entry_point.endswith(".py") and is_space:
|
| 234 |
src = os.path.join(tmp_dir, entry_point)
|
| 235 |
dst = os.path.join(tmp_dir, "app.py")
|
| 236 |
if os.path.exists(src) and not os.path.exists(dst):
|
|
@@ -238,13 +268,19 @@ def push_to_huggingface(
|
|
| 238 |
shutil.copy2(src, dst)
|
| 239 |
|
| 240 |
# Determine app_file for README
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
|
|
|
|
|
|
| 244 |
if "index.html" in files:
|
| 245 |
app_file = "index.html"
|
| 246 |
elif any(f.endswith(".html") for f in files):
|
| 247 |
app_file = next(f for f in files if f.endswith(".html"))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
# Add README.md if not present
|
| 250 |
readme_path = os.path.join(tmp_dir, "README.md")
|
|
@@ -281,7 +317,7 @@ Generated by Fullstack Code Builder using {MODEL_ID}.
|
|
| 281 |
req_path = os.path.join(tmp_dir, "requirements.txt")
|
| 282 |
has_python = any(f.endswith(".py") for f in files.keys())
|
| 283 |
|
| 284 |
-
if has_python:
|
| 285 |
# Scan all Python code for imports
|
| 286 |
all_py_code = "\n".join(
|
| 287 |
content for fname, content in files.items()
|
|
@@ -296,9 +332,6 @@ Generated by Fullstack Code Builder using {MODEL_ID}.
|
|
| 296 |
for line in (existing_reqs + "\n" + auto_reqs).splitlines():
|
| 297 |
line = line.strip()
|
| 298 |
if line and not line.startswith("#"):
|
| 299 |
-
# Normalize: take the package name (before >=, ==, etc.)
|
| 300 |
-
pkg_name = re.split(r"[><=!~]", line)[0].strip().lower()
|
| 301 |
-
# Keep the more specific version spec
|
| 302 |
merged.add(line)
|
| 303 |
|
| 304 |
Path(req_path).write_text("\n".join(sorted(merged)) + "\n", encoding="utf-8")
|
|
|
|
| 15 |
from typing import Any
|
| 16 |
|
| 17 |
from code.config.constants import MODEL_ID
|
| 18 |
+
from code.huggingface.dockerfile_gen import (
|
| 19 |
+
detect_framework,
|
| 20 |
+
is_js_project,
|
| 21 |
+
scaffold_js_project,
|
| 22 |
+
)
|
| 23 |
|
| 24 |
logger = logging.getLogger(__name__)
|
| 25 |
|
|
|
|
| 122 |
|
| 123 |
Looks for app.py, main.py, or any Python file with a launcher pattern.
|
| 124 |
"""
|
| 125 |
+
# Priority order for Python entry points
|
| 126 |
candidates = ["app.py", "main.py", "index.py", "server.py", "run.py"]
|
| 127 |
for c in candidates:
|
| 128 |
if c in files:
|
| 129 |
return c
|
| 130 |
|
| 131 |
+
# Priority order for JS entry points
|
| 132 |
+
js_candidates = ["index.js", "server.js", "src/index.js", "src/main.jsx", "src/main.tsx"]
|
| 133 |
+
for c in js_candidates:
|
| 134 |
+
if c in files:
|
| 135 |
+
return c
|
| 136 |
+
|
| 137 |
# Look for any .py file with if __name__ == "__main__" or .launch()
|
| 138 |
for fname, content in files.items():
|
| 139 |
if fname.endswith(".py"):
|
|
|
|
| 157 |
return "streamlit"
|
| 158 |
if "import gradio" in all_code or "from gradio" in all_code:
|
| 159 |
return "gradio"
|
| 160 |
+
|
| 161 |
+
# JS/TS projects β Docker
|
| 162 |
+
if is_js_project(files):
|
| 163 |
+
return "docker"
|
| 164 |
+
|
| 165 |
if any(f.endswith(".html") for f in files):
|
| 166 |
return "static"
|
| 167 |
if entry.endswith(".py"):
|
|
|
|
| 196 |
"""Push generated project to HuggingFace Hub.
|
| 197 |
|
| 198 |
Creates the repo if it doesn't exist, writes all files,
|
| 199 |
+
and adds README.md, Dockerfile, package.json, and requirements.txt as needed.
|
| 200 |
"""
|
| 201 |
try:
|
| 202 |
from huggingface_hub import HfApi, create_repo
|
|
|
|
| 215 |
entry_point = _find_entry_point(files)
|
| 216 |
detected_sdk = _detect_sdk(files, entry_point)
|
| 217 |
|
| 218 |
+
# Use detected SDK if user left it as "static" but project needs something else
|
| 219 |
if space_sdk == "static" and detected_sdk != "static":
|
| 220 |
space_sdk = detected_sdk
|
| 221 |
|
| 222 |
+
# For JS projects, scaffold Docker support files
|
| 223 |
+
if is_js_project(files) or space_sdk == "docker":
|
| 224 |
+
framework = detect_framework(files)
|
| 225 |
+
if framework == "static":
|
| 226 |
+
# Single HTML file or simple JS β keep as static
|
| 227 |
+
if any(f.endswith(".html") for f in files) and not is_js_project(files):
|
| 228 |
+
space_sdk = "static"
|
| 229 |
+
else:
|
| 230 |
+
framework = "nodejs"
|
| 231 |
+
space_sdk = "docker"
|
| 232 |
+
|
| 233 |
+
if space_sdk == "docker":
|
| 234 |
+
files = scaffold_js_project(files, framework, project_name)
|
| 235 |
+
|
| 236 |
try:
|
| 237 |
if is_space:
|
| 238 |
create_repo(
|
|
|
|
| 259 |
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
| 260 |
Path(full_path).write_text(content, encoding="utf-8")
|
| 261 |
|
| 262 |
+
# Ensure the entry point is named app.py for HF Spaces (Python)
|
| 263 |
+
if entry_point != "app.py" and entry_point.endswith(".py") and is_space and space_sdk in ("gradio", "streamlit"):
|
| 264 |
src = os.path.join(tmp_dir, entry_point)
|
| 265 |
dst = os.path.join(tmp_dir, "app.py")
|
| 266 |
if os.path.exists(src) and not os.path.exists(dst):
|
|
|
|
| 268 |
shutil.copy2(src, dst)
|
| 269 |
|
| 270 |
# Determine app_file for README
|
| 271 |
+
if space_sdk == "docker":
|
| 272 |
+
app_file = "Dockerfile"
|
| 273 |
+
elif space_sdk in ("gradio", "streamlit"):
|
| 274 |
+
app_file = "app.py"
|
| 275 |
+
elif space_sdk == "static":
|
| 276 |
if "index.html" in files:
|
| 277 |
app_file = "index.html"
|
| 278 |
elif any(f.endswith(".html") for f in files):
|
| 279 |
app_file = next(f for f in files if f.endswith(".html"))
|
| 280 |
+
else:
|
| 281 |
+
app_file = entry_point
|
| 282 |
+
else:
|
| 283 |
+
app_file = entry_point
|
| 284 |
|
| 285 |
# Add README.md if not present
|
| 286 |
readme_path = os.path.join(tmp_dir, "README.md")
|
|
|
|
| 317 |
req_path = os.path.join(tmp_dir, "requirements.txt")
|
| 318 |
has_python = any(f.endswith(".py") for f in files.keys())
|
| 319 |
|
| 320 |
+
if has_python and space_sdk != "docker":
|
| 321 |
# Scan all Python code for imports
|
| 322 |
all_py_code = "\n".join(
|
| 323 |
content for fname, content in files.items()
|
|
|
|
| 332 |
for line in (existing_reqs + "\n" + auto_reqs).splitlines():
|
| 333 |
line = line.strip()
|
| 334 |
if line and not line.startswith("#"):
|
|
|
|
|
|
|
|
|
|
| 335 |
merged.add(line)
|
| 336 |
|
| 337 |
Path(req_path).write_text("\n".join(sorted(merged)) + "\n", encoding="utf-8")
|
code/server/routes.py
CHANGED
|
@@ -475,7 +475,7 @@ def handle_push_hf(
|
|
| 475 |
exec_context_json: str,
|
| 476 |
repo_name: str,
|
| 477 |
hf_token: str,
|
| 478 |
-
space_sdk: str = "
|
| 479 |
is_space: str = "true",
|
| 480 |
) -> str:
|
| 481 |
"""Push generated project to HuggingFace Hub."""
|
|
@@ -488,13 +488,22 @@ def handle_push_hf(
|
|
| 488 |
if not project_files and code:
|
| 489 |
lang = execution_context.get("language", "python")
|
| 490 |
is_gradio = execution_context.get("is_gradio", False)
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
"
|
| 496 |
-
|
| 497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
project_files = {filename: code}
|
| 499 |
|
| 500 |
# Auto-detect SDK for Gradio apps
|
|
@@ -513,6 +522,10 @@ def handle_push_hf(
|
|
| 513 |
})
|
| 514 |
return
|
| 515 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
project_name = repo_name.split("/")[-1] if "/" in repo_name else repo_name
|
| 517 |
|
| 518 |
result = push_to_huggingface(
|
|
|
|
| 475 |
exec_context_json: str,
|
| 476 |
repo_name: str,
|
| 477 |
hf_token: str,
|
| 478 |
+
space_sdk: str = "auto",
|
| 479 |
is_space: str = "true",
|
| 480 |
) -> str:
|
| 481 |
"""Push generated project to HuggingFace Hub."""
|
|
|
|
| 488 |
if not project_files and code:
|
| 489 |
lang = execution_context.get("language", "python")
|
| 490 |
is_gradio = execution_context.get("is_gradio", False)
|
| 491 |
+
|
| 492 |
+
# Map language to entry file β JS/TS single-files get wrapped for Docker
|
| 493 |
+
if lang in ("javascript", "js", "typescript", "ts"):
|
| 494 |
+
# For single-file JS/TS code that is HTML (vanilla), keep as index.html
|
| 495 |
+
if "<!doctype" in code.lower() or "<html" in code.lower():
|
| 496 |
+
filename = "index.html"
|
| 497 |
+
else:
|
| 498 |
+
filename = "index.js"
|
| 499 |
+
elif lang in ("html", "web"):
|
| 500 |
+
filename = "index.html"
|
| 501 |
+
else:
|
| 502 |
+
ext_map = {
|
| 503 |
+
"python": "app.py", "py": "app.py",
|
| 504 |
+
}
|
| 505 |
+
filename = ext_map.get(lang, "app.py")
|
| 506 |
+
|
| 507 |
project_files = {filename: code}
|
| 508 |
|
| 509 |
# Auto-detect SDK for Gradio apps
|
|
|
|
| 522 |
})
|
| 523 |
return
|
| 524 |
|
| 525 |
+
# "auto" SDK means let push_to_huggingface decide
|
| 526 |
+
if space_sdk == "auto":
|
| 527 |
+
space_sdk = "static" # push_to_huggingface will auto-detect from files
|
| 528 |
+
|
| 529 |
project_name = repo_name.split("/")[-1] if "/" in repo_name else repo_name
|
| 530 |
|
| 531 |
result = push_to_huggingface(
|
index.html
CHANGED
|
@@ -1202,11 +1202,13 @@ body.hide-thinking .think-block { display: none; }
|
|
| 1202 |
<div class="deploy-field">
|
| 1203 |
<label for="hf-space-sdk">Space SDK</label>
|
| 1204 |
<select id="hf-space-sdk">
|
|
|
|
|
|
|
| 1205 |
<option value="static">Static (HTML/CSS/JS)</option>
|
| 1206 |
-
<option value="gradio">Gradio</option>
|
| 1207 |
-
<option value="streamlit">Streamlit</option>
|
| 1208 |
-
<option value="docker">Docker</option>
|
| 1209 |
</select>
|
|
|
|
| 1210 |
</div>
|
| 1211 |
<button id="btn-push-hf" onclick="pushToHuggingFace()" disabled>🚀 Push to HuggingFace</button>
|
| 1212 |
<div class="deploy-status" id="deploy-status"></div>
|
|
@@ -1445,7 +1447,37 @@ function onLanguageChange() {
|
|
| 1445 |
|
| 1446 |
fwSelect.onchange = () => {
|
| 1447 |
state.targetFramework = fwSelect.value;
|
|
|
|
| 1448 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1449 |
}
|
| 1450 |
|
| 1451 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 1202 |
<div class="deploy-field">
|
| 1203 |
<label for="hf-space-sdk">Space SDK</label>
|
| 1204 |
<select id="hf-space-sdk">
|
| 1205 |
+
<option value="auto">Auto-detect</option>
|
| 1206 |
+
<option value="docker">Docker (React/Next/Vue/Express/Node)</option>
|
| 1207 |
<option value="static">Static (HTML/CSS/JS)</option>
|
| 1208 |
+
<option value="gradio">Gradio (Python)</option>
|
| 1209 |
+
<option value="streamlit">Streamlit (Python)</option>
|
|
|
|
| 1210 |
</select>
|
| 1211 |
+
<div class="deploy-hint">JS frameworks auto-use Docker with Dockerfile build</div>
|
| 1212 |
</div>
|
| 1213 |
<button id="btn-push-hf" onclick="pushToHuggingFace()" disabled>🚀 Push to HuggingFace</button>
|
| 1214 |
<div class="deploy-status" id="deploy-status"></div>
|
|
|
|
| 1447 |
|
| 1448 |
fwSelect.onchange = () => {
|
| 1449 |
state.targetFramework = fwSelect.value;
|
| 1450 |
+
autoSelectSDK(selectedLang, fwSelect.value);
|
| 1451 |
};
|
| 1452 |
+
|
| 1453 |
+
// Auto-select SDK based on language/framework
|
| 1454 |
+
autoSelectSDK(selectedLang, fwSelect.value);
|
| 1455 |
+
}
|
| 1456 |
+
|
| 1457 |
+
function autoSelectSDK(lang, framework) {
|
| 1458 |
+
const sdkSelect = document.getElementById('hf-space-sdk');
|
| 1459 |
+
if (!sdkSelect) return;
|
| 1460 |
+
|
| 1461 |
+
const jsLangs = ['JavaScript', 'TypeScript'];
|
| 1462 |
+
const jsFrameworks = ['React', 'Next.js', 'Vue.js', 'Express.js', 'Node.js', 'NestJS'];
|
| 1463 |
+
const pythonFrameworks = ['Gradio', 'Streamlit', 'Flask', 'Django', 'FastAPI'];
|
| 1464 |
+
|
| 1465 |
+
if (jsLangs.includes(lang) || jsFrameworks.includes(framework)) {
|
| 1466 |
+
// JS frameworks need Docker
|
| 1467 |
+
if (jsFrameworks.includes(framework) || jsLangs.includes(lang)) {
|
| 1468 |
+
sdkSelect.value = 'docker';
|
| 1469 |
+
}
|
| 1470 |
+
} else if (framework === 'Gradio') {
|
| 1471 |
+
sdkSelect.value = 'gradio';
|
| 1472 |
+
} else if (framework === 'Streamlit') {
|
| 1473 |
+
sdkSelect.value = 'streamlit';
|
| 1474 |
+
} else if (lang === 'Python' && pythonFrameworks.includes(framework)) {
|
| 1475 |
+
sdkSelect.value = 'gradio';
|
| 1476 |
+
} else if (lang === 'HTML/CSS/JS' || framework === 'Vanilla' || framework === 'Tailwind CSS' || framework === 'Bootstrap') {
|
| 1477 |
+
sdkSelect.value = 'static';
|
| 1478 |
+
} else {
|
| 1479 |
+
sdkSelect.value = 'auto';
|
| 1480 |
+
}
|
| 1481 |
}
|
| 1482 |
|
| 1483 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|