JakgritB Claude Sonnet 4.6 commited on
Commit ·
f066cd2
1
Parent(s): 70fbcf2
fix(hf-space): add HF Spaces config and root Dockerfile
Browse files- Add YAML frontmatter to README.md (sdk: docker) so HF Space
recognises the repo as a Docker-based Space
- Add root Dockerfile: multi-stage build that compiles React with
VITE_API_BASE_URL="" then runs FastAPI on port 7860 (HF default)
- FastAPI now serves the built React SPA as a catch-all when
/app/static/ exists, keeping dev setup unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile +35 -0
- README.md +9 -0
- backend/app/main.py +13 -0
Dockerfile
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stage 1: build React frontend
|
| 2 |
+
FROM node:22-alpine AS frontend-builder
|
| 3 |
+
WORKDIR /frontend
|
| 4 |
+
COPY frontend/package*.json ./
|
| 5 |
+
RUN npm ci
|
| 6 |
+
COPY frontend/ ./
|
| 7 |
+
# Empty string = same-origin API calls (FastAPI serves both)
|
| 8 |
+
ENV VITE_API_BASE_URL=""
|
| 9 |
+
RUN npm run build
|
| 10 |
+
|
| 11 |
+
# Stage 2: FastAPI backend (CPU, demo mode for HF Space)
|
| 12 |
+
FROM python:3.11-slim
|
| 13 |
+
WORKDIR /app
|
| 14 |
+
|
| 15 |
+
RUN apt-get update && \
|
| 16 |
+
apt-get install -y --no-install-recommends ffmpeg git curl && \
|
| 17 |
+
rm -rf /var/lib/apt/lists/*
|
| 18 |
+
|
| 19 |
+
COPY backend/pyproject.toml ./
|
| 20 |
+
RUN pip install --no-cache-dir -e "."
|
| 21 |
+
|
| 22 |
+
COPY backend/app ./app
|
| 23 |
+
|
| 24 |
+
# Copy built frontend so FastAPI can serve it
|
| 25 |
+
COPY --from=frontend-builder /frontend/dist ./static
|
| 26 |
+
|
| 27 |
+
RUN mkdir -p /app/data
|
| 28 |
+
|
| 29 |
+
ENV DEMO_MODE=true
|
| 30 |
+
ENV STORAGE_DIR=/app/data
|
| 31 |
+
ENV FRONTEND_ORIGIN=https://elevenclip-ai.hf.space
|
| 32 |
+
|
| 33 |
+
EXPOSE 7860
|
| 34 |
+
|
| 35 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# ElevenClip.AI
|
| 2 |
|
| 3 |
ElevenClip.AI is an AI-powered clip studio for turning long-form videos into personalized short-form content for TikTok, YouTube Shorts, and Instagram Reels.
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ElevenClip AI
|
| 3 |
+
emoji: 🎬
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: red
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
# ElevenClip.AI
|
| 11 |
|
| 12 |
ElevenClip.AI is an AI-powered clip studio for turning long-form videos into personalized short-form content for TikTok, YouTube Shorts, and Instagram Reels.
|
backend/app/main.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
from fastapi import BackgroundTasks, FastAPI, File, Form, HTTPException, UploadFile
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from fastapi.responses import FileResponse
|
|
@@ -32,6 +34,17 @@ app.add_middleware(
|
|
| 32 |
)
|
| 33 |
app.mount("/media", StaticFiles(directory=settings.storage_dir), name="media")
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
@app.get("/health", response_model=HealthResponse)
|
| 37 |
async def health() -> HealthResponse:
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
from fastapi import BackgroundTasks, FastAPI, File, Form, HTTPException, UploadFile
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
from fastapi.responses import FileResponse
|
|
|
|
| 34 |
)
|
| 35 |
app.mount("/media", StaticFiles(directory=settings.storage_dir), name="media")
|
| 36 |
|
| 37 |
+
_STATIC_DIR = Path(__file__).parent.parent.parent / "static"
|
| 38 |
+
if _STATIC_DIR.is_dir():
|
| 39 |
+
app.mount("/assets", StaticFiles(directory=_STATIC_DIR / "assets"), name="frontend-assets")
|
| 40 |
+
|
| 41 |
+
@app.get("/{full_path:path}", include_in_schema=False)
|
| 42 |
+
async def serve_spa(full_path: str) -> FileResponse:
|
| 43 |
+
candidate = _STATIC_DIR / full_path
|
| 44 |
+
if candidate.is_file():
|
| 45 |
+
return FileResponse(candidate)
|
| 46 |
+
return FileResponse(_STATIC_DIR / "index.html")
|
| 47 |
+
|
| 48 |
|
| 49 |
@app.get("/health", response_model=HealthResponse)
|
| 50 |
async def health() -> HealthResponse:
|