Spaces:
Sleeping
Sleeping
Lcmind commited on
Commit Β·
6849bff
0
Parent(s):
π Production-ready backend with Flux.1-schnell optimization
Browse files- .env.example +14 -0
- .gitignore +58 -0
- Dockerfile +62 -0
- README.md +198 -0
- main.py +485 -0
- requirements.txt +27 -0
.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# VIBE_LINK Backend - Environment Variables
|
| 2 |
+
# Copy this file to .env and fill in your API keys
|
| 3 |
+
|
| 4 |
+
# Hugging Face API Token
|
| 5 |
+
# Get it from: https://huggingface.co/settings/tokens
|
| 6 |
+
HF_TOKEN=your_huggingface_token_here
|
| 7 |
+
|
| 8 |
+
# Google Gemini API Key
|
| 9 |
+
# Get it from: https://aistudio.google.com/app/apikey
|
| 10 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 11 |
+
|
| 12 |
+
# ImgBB API Key
|
| 13 |
+
# Get it from: https://api.imgbb.com/
|
| 14 |
+
IMGBB_KEY=your_imgbb_api_key_here
|
.gitignore
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual Environments
|
| 24 |
+
venv/
|
| 25 |
+
ENV/
|
| 26 |
+
env/
|
| 27 |
+
.venv
|
| 28 |
+
|
| 29 |
+
# Environment Variables
|
| 30 |
+
.env
|
| 31 |
+
.env.local
|
| 32 |
+
|
| 33 |
+
# IDE
|
| 34 |
+
.vscode/
|
| 35 |
+
.idea/
|
| 36 |
+
*.swp
|
| 37 |
+
*.swo
|
| 38 |
+
*~
|
| 39 |
+
|
| 40 |
+
# OS
|
| 41 |
+
.DS_Store
|
| 42 |
+
Thumbs.db
|
| 43 |
+
|
| 44 |
+
# Temp Files
|
| 45 |
+
*.log
|
| 46 |
+
*.tmp
|
| 47 |
+
temp/
|
| 48 |
+
tmp/
|
| 49 |
+
screenshot_*.jpg
|
| 50 |
+
poster_*.webp
|
| 51 |
+
|
| 52 |
+
# Testing
|
| 53 |
+
.pytest_cache/
|
| 54 |
+
.coverage
|
| 55 |
+
htmlcov/
|
| 56 |
+
|
| 57 |
+
# Hugging Face
|
| 58 |
+
.huggingface/
|
Dockerfile
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# VIBE_LINK Backend - Optimized Production Dockerfile
|
| 2 |
+
# Designed for Hugging Face Spaces with Headless Chrome support
|
| 3 |
+
|
| 4 |
+
FROM python:3.9-slim
|
| 5 |
+
|
| 6 |
+
# Set environment variables for optimization
|
| 7 |
+
ENV PYTHONUNBUFFERED=1 \
|
| 8 |
+
PYTHONDONTWRITEBYTECODE=1 \
|
| 9 |
+
PIP_NO_CACHE_DIR=1 \
|
| 10 |
+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 11 |
+
DEBIAN_FRONTEND=noninteractive
|
| 12 |
+
|
| 13 |
+
# Install system dependencies for Chromium (minimal set)
|
| 14 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 15 |
+
chromium \
|
| 16 |
+
chromium-driver \
|
| 17 |
+
fonts-liberation \
|
| 18 |
+
libnss3 \
|
| 19 |
+
libxss1 \
|
| 20 |
+
libasound2 \
|
| 21 |
+
libatk-bridge2.0-0 \
|
| 22 |
+
libatk1.0-0 \
|
| 23 |
+
libcups2 \
|
| 24 |
+
libdbus-1-3 \
|
| 25 |
+
libdrm2 \
|
| 26 |
+
libgbm1 \
|
| 27 |
+
libgtk-3-0 \
|
| 28 |
+
libnspr4 \
|
| 29 |
+
libx11-xcb1 \
|
| 30 |
+
libxcomposite1 \
|
| 31 |
+
libxdamage1 \
|
| 32 |
+
libxrandr2 \
|
| 33 |
+
xdg-utils \
|
| 34 |
+
&& apt-get clean \
|
| 35 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 36 |
+
|
| 37 |
+
# Set Chrome environment variables
|
| 38 |
+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
| 39 |
+
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
| 40 |
+
|
| 41 |
+
# Create app directory with proper permissions
|
| 42 |
+
WORKDIR /app
|
| 43 |
+
RUN chmod 777 /app
|
| 44 |
+
|
| 45 |
+
# Copy dependency files first (layer caching optimization)
|
| 46 |
+
COPY requirements.txt .
|
| 47 |
+
|
| 48 |
+
# Install Python dependencies
|
| 49 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 50 |
+
|
| 51 |
+
# Copy application code
|
| 52 |
+
COPY . .
|
| 53 |
+
|
| 54 |
+
# Expose Hugging Face Spaces port
|
| 55 |
+
EXPOSE 7860
|
| 56 |
+
|
| 57 |
+
# Health check for container monitoring
|
| 58 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 59 |
+
CMD python -c "import requests; requests.get('http://localhost:7860/health', timeout=5)"
|
| 60 |
+
|
| 61 |
+
# Run FastAPI with production settings
|
| 62 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# π¨ VIBE_LINK Backend
|
| 2 |
+
|
| 3 |
+
AI-powered serverless API that transforms website URLs into stunning "Vibe Poster" images using Google Gemini and Hugging Face Flux.1.
|
| 4 |
+
|
| 5 |
+
## π Features
|
| 6 |
+
|
| 7 |
+
- **Screenshot Capture**: Headless Chrome (pyppeteer) for high-quality website screenshots
|
| 8 |
+
- **AI Analysis**: Google Gemini 2.5 Flash extracts design vibe and generates artistic prompts
|
| 9 |
+
- **Image Generation**: Hugging Face Flux.1-dev creates 3D abstract posters
|
| 10 |
+
- **Cloud Hosting**: ImgBB API for permanent image storage
|
| 11 |
+
- **Optimized**: Production-ready with minimal resource footprint
|
| 12 |
+
|
| 13 |
+
## ποΈ Tech Stack
|
| 14 |
+
|
| 15 |
+
- **Framework**: FastAPI + Uvicorn
|
| 16 |
+
- **AI Models**:
|
| 17 |
+
- Google Gemini 2.0 Flash Exp (vision analysis)
|
| 18 |
+
- FLUX.1-dev (image generation)
|
| 19 |
+
- **Infrastructure**: Docker (Hugging Face Spaces)
|
| 20 |
+
- **Language**: Python 3.9
|
| 21 |
+
|
| 22 |
+
## π¦ Installation
|
| 23 |
+
|
| 24 |
+
### 1. Clone Repository
|
| 25 |
+
```bash
|
| 26 |
+
git clone https://github.com/Lcmind/vibe-link-backend.git
|
| 27 |
+
cd vibe-link-backend
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
### 2. Set Environment Variables
|
| 31 |
+
```bash
|
| 32 |
+
cp .env.example .env
|
| 33 |
+
# Edit .env and add your API keys:
|
| 34 |
+
# - HF_TOKEN (Hugging Face)
|
| 35 |
+
# - GEMINI_API_KEY (Google AI Studio)
|
| 36 |
+
# - IMGBB_KEY (ImgBB)
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### 3. Run Locally (Docker)
|
| 40 |
+
```bash
|
| 41 |
+
docker build -t vibe-link-backend .
|
| 42 |
+
docker run -p 7860:7860 --env-file .env vibe-link-backend
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
### 4. Run Locally (Python)
|
| 46 |
+
```bash
|
| 47 |
+
python -m venv venv
|
| 48 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 49 |
+
pip install -r requirements.txt
|
| 50 |
+
uvicorn main:app --reload --port 7860
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## π API Usage
|
| 54 |
+
|
| 55 |
+
### POST /create
|
| 56 |
+
Generate a vibe poster from a website URL.
|
| 57 |
+
|
| 58 |
+
**Request:**
|
| 59 |
+
```json
|
| 60 |
+
{
|
| 61 |
+
"url": "https://example.com"
|
| 62 |
+
}
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**Response:**
|
| 66 |
+
```json
|
| 67 |
+
{
|
| 68 |
+
"status": "success",
|
| 69 |
+
"poster_url": "https://i.ibb.co/abc123/poster.webp",
|
| 70 |
+
"vibe": "Minimalist",
|
| 71 |
+
"summary": "κΉλν λμμΈκ³Ό λͺ
νν νμ΄ν¬κ·ΈλνΌκ° λ보μ΄λ νλμ μΈ μΉμ¬μ΄νΈ"
|
| 72 |
+
}
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### GET /health
|
| 76 |
+
Health check endpoint.
|
| 77 |
+
|
| 78 |
+
**Response:**
|
| 79 |
+
```json
|
| 80 |
+
{
|
| 81 |
+
"status": "healthy",
|
| 82 |
+
"service": "vibe-link-backend"
|
| 83 |
+
}
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## π― Deployment to Hugging Face Spaces
|
| 87 |
+
|
| 88 |
+
### 1. Create a New Space
|
| 89 |
+
1. Go to [Hugging Face Spaces](https://huggingface.co/spaces)
|
| 90 |
+
2. Click **"Create new Space"**
|
| 91 |
+
3. Select **Docker** as SDK
|
| 92 |
+
4. Name: `vibe-link-backend`
|
| 93 |
+
|
| 94 |
+
### 2. Push Code to HF Space
|
| 95 |
+
```bash
|
| 96 |
+
# Add Hugging Face as remote
|
| 97 |
+
git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/vibe-link-backend
|
| 98 |
+
git push hf main
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### 3. Configure Secrets
|
| 102 |
+
In Space Settings β Repository Secrets, add:
|
| 103 |
+
- `HF_TOKEN`
|
| 104 |
+
- `GEMINI_API_KEY`
|
| 105 |
+
- `IMGBB_KEY`
|
| 106 |
+
|
| 107 |
+
### 4. Access Your API
|
| 108 |
+
```
|
| 109 |
+
https://YOUR_USERNAME-vibe-link-backend.hf.space/
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
## π§ Configuration
|
| 113 |
+
|
| 114 |
+
### Environment Variables
|
| 115 |
+
| Variable | Description | Required |
|
| 116 |
+
|----------|-------------|----------|
|
| 117 |
+
| `HF_TOKEN` | Hugging Face API token | β
|
|
| 118 |
+
| `GEMINI_API_KEY` | Google Gemini API key | β
|
|
| 119 |
+
| `IMGBB_KEY` | ImgBB API key | β
|
|
| 120 |
+
|
| 121 |
+
### Get API Keys
|
| 122 |
+
- **Hugging Face**: https://huggingface.co/settings/tokens
|
| 123 |
+
- **Google Gemini**: https://aistudio.google.com/app/apikey
|
| 124 |
+
- **ImgBB**: https://api.imgbb.com/
|
| 125 |
+
|
| 126 |
+
## π Pipeline Architecture
|
| 127 |
+
|
| 128 |
+
```
|
| 129 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 130 |
+
β POST /create { "url": "https://example.com" } β
|
| 131 |
+
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
|
| 132 |
+
β
|
| 133 |
+
ββββββββββββββββββΌβββββββββββββββββ
|
| 134 |
+
β STEP 1: Screenshot Capture β
|
| 135 |
+
β Tool: pyppeteer β
|
| 136 |
+
β Output: screenshot.jpg β
|
| 137 |
+
ββββββββββββββββββ¬βββββββββββββββββ
|
| 138 |
+
β
|
| 139 |
+
ββββββββββββββββββΌβββββββββββββββββ
|
| 140 |
+
β STEP 2: AI Analysis β
|
| 141 |
+
β Tool: Google Gemini 2.5 Flash β
|
| 142 |
+
β Output: vibe + flux_prompt β
|
| 143 |
+
ββββββββββββββββββ¬βββββββββββββββββ
|
| 144 |
+
β
|
| 145 |
+
ββββββββββββββββββΌβββββββββββββββββ
|
| 146 |
+
β STEP 3: Image Generation β
|
| 147 |
+
β Tool: HF Flux.1-dev β
|
| 148 |
+
β Output: poster.webp β
|
| 149 |
+
ββββββββββββββββββ¬βββββββββββββββββ
|
| 150 |
+
β
|
| 151 |
+
ββββββββββββββββββΌβββββββββββββββββ
|
| 152 |
+
β STEP 4: Upload to ImgBB β
|
| 153 |
+
β Tool: ImgBB API β
|
| 154 |
+
β Output: public URL β
|
| 155 |
+
ββββββββββββββββββ¬βββββββββββββββββ
|
| 156 |
+
β
|
| 157 |
+
ββββββββββββββββββΌβββββββββββββββββ
|
| 158 |
+
β Response: { poster_url } β
|
| 159 |
+
βββββββββββββββββββββββββββββββββββ
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
## π οΈ Development
|
| 163 |
+
|
| 164 |
+
### Project Structure
|
| 165 |
+
```
|
| 166 |
+
vibe-link-backend/
|
| 167 |
+
βββ main.py # FastAPI application
|
| 168 |
+
βββ requirements.txt # Python dependencies
|
| 169 |
+
βββ Dockerfile # Docker configuration
|
| 170 |
+
βββ .env.example # Environment template
|
| 171 |
+
βββ .gitignore # Git ignore rules
|
| 172 |
+
βββ README.md # Documentation
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
### Code Quality Features
|
| 176 |
+
- β
Type hints (Pydantic models)
|
| 177 |
+
- β
Error handling & logging
|
| 178 |
+
- β
Resource cleanup (temp files)
|
| 179 |
+
- β
Docker health checks
|
| 180 |
+
- β
Production-ready CORS
|
| 181 |
+
- β
Memory-optimized Chrome args
|
| 182 |
+
|
| 183 |
+
## π License
|
| 184 |
+
|
| 185 |
+
MIT License - feel free to use for your projects!
|
| 186 |
+
|
| 187 |
+
## π€ Contributing
|
| 188 |
+
|
| 189 |
+
Contributions welcome! Please open an issue or PR.
|
| 190 |
+
|
| 191 |
+
## π§ Support
|
| 192 |
+
|
| 193 |
+
For issues or questions, open a GitHub issue at:
|
| 194 |
+
https://github.com/Lcmind/vibe-link-backend/issues
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
**Built with β€οΈ by S-Grade Developer**
|
main.py
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
VIBE_LINK Backend API
|
| 3 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 4 |
+
Serverless-style FastAPI backend for converting websites into Vibe Posters.
|
| 5 |
+
Optimized for Hugging Face Spaces (Docker SDK) with 16GB RAM.
|
| 6 |
+
|
| 7 |
+
Architecture:
|
| 8 |
+
1. Screenshot Capture β pyppeteer (Headless Chrome)
|
| 9 |
+
2. AI Analysis β Google Gemini 2.5 Flash
|
| 10 |
+
3. Image Generation β Hugging Face Flux.1-dev
|
| 11 |
+
4. Image Hosting β ImgBB API
|
| 12 |
+
|
| 13 |
+
Author: S-Grade Developer | Production-Ready Code
|
| 14 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import json
|
| 19 |
+
import base64
|
| 20 |
+
import asyncio
|
| 21 |
+
import tempfile
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from typing import Optional, Dict
|
| 24 |
+
from contextlib import asynccontextmanager
|
| 25 |
+
|
| 26 |
+
import requests
|
| 27 |
+
from fastapi import FastAPI, HTTPException, Request
|
| 28 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 29 |
+
from fastapi.responses import JSONResponse
|
| 30 |
+
from pydantic import BaseModel, HttpUrl, Field
|
| 31 |
+
from pyppeteer import launch
|
| 32 |
+
from huggingface_hub import InferenceClient
|
| 33 |
+
import google.generativeai as genai
|
| 34 |
+
from PIL import Image
|
| 35 |
+
from dotenv import load_dotenv
|
| 36 |
+
|
| 37 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
+
# CONFIGURATION & ENVIRONMENT
|
| 39 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 40 |
+
|
| 41 |
+
load_dotenv()
|
| 42 |
+
|
| 43 |
+
# API Keys (Secured via Environment Variables)
|
| 44 |
+
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 45 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 46 |
+
IMGBB_KEY = os.getenv("IMGBB_KEY")
|
| 47 |
+
|
| 48 |
+
# Validate API Keys
|
| 49 |
+
if not all([HF_TOKEN, GEMINI_API_KEY, IMGBB_KEY]):
|
| 50 |
+
raise RuntimeError(
|
| 51 |
+
"Missing API Keys! Set HF_TOKEN, GEMINI_API_KEY, IMGBB_KEY in environment."
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
# Initialize AI Services
|
| 55 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
| 56 |
+
hf_client = InferenceClient(token=HF_TOKEN)
|
| 57 |
+
|
| 58 |
+
# Prevent pyppeteer from downloading Chromium (use system-installed one)
|
| 59 |
+
os.environ['PUPPETEER_SKIP_CHROMIUM_DOWNLOAD'] = 'true'
|
| 60 |
+
os.environ['PUPPETEER_EXECUTABLE_PATH'] = '/usr/bin/chromium'
|
| 61 |
+
|
| 62 |
+
# Constants
|
| 63 |
+
SCREENSHOT_TIMEOUT = 20000 # 20 seconds
|
| 64 |
+
VIEWPORT_WIDTH = 1280
|
| 65 |
+
VIEWPORT_HEIGHT = 1200
|
| 66 |
+
FLUX_MODEL = "black-forest-labs/FLUX.1-schnell" # Faster model, same quality (4 steps vs 30)
|
| 67 |
+
TEMP_DIR = Path(tempfile.gettempdir()) / "vibe_link"
|
| 68 |
+
TEMP_DIR.mkdir(exist_ok=True)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 72 |
+
# PYDANTIC MODELS
|
| 73 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 74 |
+
|
| 75 |
+
class CreateRequest(BaseModel):
|
| 76 |
+
url: HttpUrl = Field(..., description="Target website URL to analyze")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class CreateResponse(BaseModel):
|
| 80 |
+
status: str
|
| 81 |
+
poster_url: str
|
| 82 |
+
vibe: Optional[str] = None
|
| 83 |
+
summary: Optional[str] = None
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class ErrorResponse(BaseModel):
|
| 87 |
+
status: str = "error"
|
| 88 |
+
message: str
|
| 89 |
+
detail: Optional[str] = None
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 93 |
+
# FASTAPI LIFECYCLE MANAGEMENT
|
| 94 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 95 |
+
|
| 96 |
+
@asynccontextmanager
|
| 97 |
+
async def lifespan(app: FastAPI):
|
| 98 |
+
"""Manage browser lifecycle to minimize resource usage"""
|
| 99 |
+
print("π VIBE_LINK Backend Starting...")
|
| 100 |
+
yield
|
| 101 |
+
print("π Cleaning up resources...")
|
| 102 |
+
# Cleanup temp files on shutdown
|
| 103 |
+
for file in TEMP_DIR.glob("*"):
|
| 104 |
+
try:
|
| 105 |
+
file.unlink()
|
| 106 |
+
except Exception:
|
| 107 |
+
pass
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
app = FastAPI(
|
| 111 |
+
title="VIBE_LINK API",
|
| 112 |
+
description="AI-powered Website to Vibe Poster Generator",
|
| 113 |
+
version="1.0.0",
|
| 114 |
+
lifespan=lifespan,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
# CORS Middleware (Allow all origins for public API)
|
| 118 |
+
app.add_middleware(
|
| 119 |
+
CORSMiddleware,
|
| 120 |
+
allow_origins=["*"],
|
| 121 |
+
allow_credentials=True,
|
| 122 |
+
allow_methods=["*"],
|
| 123 |
+
allow_headers=["*"],
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 128 |
+
# CORE PIPELINE FUNCTIONS
|
| 129 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 130 |
+
|
| 131 |
+
async def capture_screenshot(url: str) -> Path:
|
| 132 |
+
"""
|
| 133 |
+
STEP 1: Screenshot Capture
|
| 134 |
+
Uses Headless Chrome to capture website screenshot.
|
| 135 |
+
Optimized for Docker environment with sandbox disabled.
|
| 136 |
+
"""
|
| 137 |
+
screenshot_path = TEMP_DIR / f"screenshot_{id(url)}.jpg"
|
| 138 |
+
|
| 139 |
+
browser = None
|
| 140 |
+
try:
|
| 141 |
+
# Launch Chromium with Docker-safe arguments
|
| 142 |
+
browser = await launch(
|
| 143 |
+
headless=True,
|
| 144 |
+
executablePath="/usr/bin/chromium",
|
| 145 |
+
args=[
|
| 146 |
+
"--no-sandbox",
|
| 147 |
+
"--disable-setuid-sandbox",
|
| 148 |
+
"--disable-dev-shm-usage",
|
| 149 |
+
"--disable-gpu",
|
| 150 |
+
"--disable-software-rasterizer",
|
| 151 |
+
"--disable-extensions",
|
| 152 |
+
"--single-process", # Memory optimization
|
| 153 |
+
],
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
page = await browser.newPage()
|
| 157 |
+
await page.setViewport({"width": VIEWPORT_WIDTH, "height": VIEWPORT_HEIGHT})
|
| 158 |
+
|
| 159 |
+
# Navigate with timeout
|
| 160 |
+
await page.goto(
|
| 161 |
+
url,
|
| 162 |
+
{"waitUntil": "domcontentloaded", "timeout": SCREENSHOT_TIMEOUT}
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
# Capture screenshot
|
| 166 |
+
await page.screenshot({"path": str(screenshot_path), "type": "jpeg", "quality": 85})
|
| 167 |
+
|
| 168 |
+
return screenshot_path
|
| 169 |
+
|
| 170 |
+
except Exception as e:
|
| 171 |
+
raise HTTPException(
|
| 172 |
+
status_code=500,
|
| 173 |
+
detail=f"Screenshot capture failed: {str(e)}"
|
| 174 |
+
)
|
| 175 |
+
finally:
|
| 176 |
+
if browser:
|
| 177 |
+
await browser.close()
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
async def analyze_with_gemini(screenshot_path: Path) -> Dict[str, str]:
|
| 181 |
+
"""
|
| 182 |
+
STEP 2: AI Analysis
|
| 183 |
+
Uses Google Gemini 2.5 Flash to analyze screenshot and generate
|
| 184 |
+
vibe description + image generation prompt.
|
| 185 |
+
"""
|
| 186 |
+
try:
|
| 187 |
+
# Upload image to Gemini
|
| 188 |
+
uploaded_file = genai.upload_file(str(screenshot_path))
|
| 189 |
+
|
| 190 |
+
# AI Analysis Prompt
|
| 191 |
+
analysis_prompt = """
|
| 192 |
+
You are a world-class Creative Director and Prompt Engineer.
|
| 193 |
+
Analyze this website screenshot and generate a high-end vertical brand poster concept for Flux.1.
|
| 194 |
+
|
| 195 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 196 |
+
STEP 1: EXTRACT BRAND IDENTITY
|
| 197 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 198 |
+
1. Find the main brand name/logo in the screenshot.
|
| 199 |
+
2. **CRITICAL: Convert Korean to English romanization.**
|
| 200 |
+
Examples:
|
| 201 |
+
- "무μ μ¬" β "MUSINSA"
|
| 202 |
+
- "λ‘λ³Άμ΄ μ²κ΅" β "TTEOKBOKKI HEAVEN"
|
| 203 |
+
- "μ΄μ°½λ―Ό" β "LEE CHANGMIN"
|
| 204 |
+
- "μΉ΄ν μμΈ" β "CAFE SEOUL"
|
| 205 |
+
3. If no brand name exists, use the domain name.
|
| 206 |
+
|
| 207 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 208 |
+
STEP 2: ANALYZE BUSINESS TYPE & VISUAL DNA
|
| 209 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 210 |
+
Identify the business category and extract visual elements:
|
| 211 |
+
|
| 212 |
+
**Business Examples:**
|
| 213 |
+
- Fashion (무μ μ¬) β Clothes, fabrics, hangers, runway
|
| 214 |
+
- Food (λ‘λ³Άμ΄) β Ingredients, steam, spices, bowls
|
| 215 |
+
- Tech (μ½λ©) β Code snippets, circuits, data streams
|
| 216 |
+
- Fitness (ν¬μ€μ₯) β Dumbbells, protein, energy
|
| 217 |
+
- Corporate (λ²λ₯ ) β Books, marble, gold, scales
|
| 218 |
+
|
| 219 |
+
**Extract:**
|
| 220 |
+
- Dominant Color Palette (2-3 colors)
|
| 221 |
+
- Key Physical Objects (what fills the background)
|
| 222 |
+
- Material/Texture (fabric, metal, food, digital)
|
| 223 |
+
- Lighting Mood (neon, sunlight, studio, dark)
|
| 224 |
+
|
| 225 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 226 |
+
STEP 3: CONSTRUCT FLUX.1 PROMPT
|
| 227 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 228 |
+
Build a detailed scene description:
|
| 229 |
+
|
| 230 |
+
**BACKGROUND (70% of prompt):**
|
| 231 |
+
- Describe a 3D environment FILLED with objects related to the business.
|
| 232 |
+
- Example (Fashion): "floating designer clothes, silk fabrics, leather jackets, sneakers, accessories scattered in mid-air"
|
| 233 |
+
- Example (Food): "steaming bowls, fresh ingredients, spices exploding, sauce splash, vibrant colors"
|
| 234 |
+
- Use the dominant colors extracted in Step 2.
|
| 235 |
+
|
| 236 |
+
**CENTER TYPOGRAPHY (30% of prompt):**
|
| 237 |
+
- Place the ENGLISH brand name in the CENTER.
|
| 238 |
+
- Make it a 3D object matching the theme:
|
| 239 |
+
* Fashion: "metallic chrome letters with fabric texture"
|
| 240 |
+
* Food: "letters made of the food itself (cookies, cake, rice)"
|
| 241 |
+
* Tech: "neon holographic glowing text"
|
| 242 |
+
- Keywords: "big bold 3D typography", "magazine cover layout", "centered composition"
|
| 243 |
+
|
| 244 |
+
**MANDATORY RULES:**
|
| 245 |
+
β
NO Korean characters (Hangul) - Flux CANNOT render them
|
| 246 |
+
β
Convert all Korean to English romanization
|
| 247 |
+
β
Brand name must be in CENTER in big 3D letters
|
| 248 |
+
β
Background must be FILLED with thematic 3D objects
|
| 249 |
+
β
Use "vertical poster", "9:16 aspect ratio", "high quality 3D render"
|
| 250 |
+
|
| 251 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 252 |
+
OUTPUT FORMAT (JSON)
|
| 253 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 254 |
+
Return ONLY valid JSON:
|
| 255 |
+
{
|
| 256 |
+
"vibe": "Business type in 1-2 English words (e.g., Fashion, Spicy Food, Tech)",
|
| 257 |
+
"summary": "One sentence description in Korean",
|
| 258 |
+
"flux_prompt": "Complete Flux.1 prompt in English following the rules above"
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
**Example flux_prompt structure:**
|
| 262 |
+
"A vertical 3D poster (9:16), [BACKGROUND: detailed scene with thematic objects], centered big bold 3D typography text '[BRAND NAME IN ENGLISH]' made of [material matching theme], [lighting description], magazine cover layout, high quality render, cinematic composition"
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
# Call Gemini API
|
| 266 |
+
model = genai.GenerativeModel("gemini-2.0-flash-exp")
|
| 267 |
+
response = model.generate_content([uploaded_file, analysis_prompt])
|
| 268 |
+
|
| 269 |
+
# Robust JSON parsing with multiple fallback strategies
|
| 270 |
+
response_text = response.text.strip()
|
| 271 |
+
|
| 272 |
+
# Strategy 1: Remove markdown code blocks
|
| 273 |
+
if response_text.startswith("```json"):
|
| 274 |
+
response_text = response_text[7:-3].strip()
|
| 275 |
+
elif response_text.startswith("```"):
|
| 276 |
+
response_text = response_text[3:-3].strip()
|
| 277 |
+
|
| 278 |
+
# Strategy 2: Find JSON object between curly braces
|
| 279 |
+
if not response_text.startswith("{"):
|
| 280 |
+
start = response_text.find("{")
|
| 281 |
+
end = response_text.rfind("}") + 1
|
| 282 |
+
if start != -1 and end > start:
|
| 283 |
+
response_text = response_text[start:end]
|
| 284 |
+
|
| 285 |
+
try:
|
| 286 |
+
analysis = json.loads(response_text)
|
| 287 |
+
except json.JSONDecodeError as e:
|
| 288 |
+
raise ValueError(f"Failed to parse Gemini JSON response: {e}. Response: {response_text[:200]}")
|
| 289 |
+
|
| 290 |
+
# Validate required fields
|
| 291 |
+
if not all(k in analysis for k in ["vibe", "summary", "flux_prompt"]):
|
| 292 |
+
raise ValueError(f"Missing required fields in Gemini response. Got: {list(analysis.keys())}")
|
| 293 |
+
|
| 294 |
+
return analysis
|
| 295 |
+
|
| 296 |
+
except Exception as e:
|
| 297 |
+
raise HTTPException(
|
| 298 |
+
status_code=500,
|
| 299 |
+
detail=f"AI analysis failed: {str(e)}"
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
async def generate_poster(flux_prompt: str) -> Path:
|
| 304 |
+
"""
|
| 305 |
+
STEP 3: Image Generation
|
| 306 |
+
Uses Hugging Face Inference API with Flux.1-dev model
|
| 307 |
+
to generate vibe poster.
|
| 308 |
+
"""
|
| 309 |
+
poster_path = TEMP_DIR / f"poster_{id(flux_prompt)}.webp"
|
| 310 |
+
|
| 311 |
+
try:
|
| 312 |
+
# Call Flux.1-schnell via HF Inference API
|
| 313 |
+
# Schnell is 10x faster than dev with similar quality
|
| 314 |
+
image = hf_client.text_to_image(
|
| 315 |
+
flux_prompt,
|
| 316 |
+
model=FLUX_MODEL,
|
| 317 |
+
# Parameters optimized for vertical poster
|
| 318 |
+
width=768,
|
| 319 |
+
height=1344, # 9:16 aspect ratio
|
| 320 |
+
num_inference_steps=4, # Schnell optimized for 1-4 steps
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
# Save as WebP for optimal compression
|
| 324 |
+
image.save(str(poster_path), "WEBP", quality=90, method=6)
|
| 325 |
+
|
| 326 |
+
return poster_path
|
| 327 |
+
|
| 328 |
+
except Exception as e:
|
| 329 |
+
raise HTTPException(
|
| 330 |
+
status_code=500,
|
| 331 |
+
detail=f"Image generation failed: {str(e)}"
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
async def upload_to_imgbb(image_path: Path) -> str:
|
| 336 |
+
"""
|
| 337 |
+
STEP 4: Image Hosting
|
| 338 |
+
Uploads generated poster to ImgBB for permanent hosting.
|
| 339 |
+
Returns public URL.
|
| 340 |
+
"""
|
| 341 |
+
try:
|
| 342 |
+
# Read image and encode as base64
|
| 343 |
+
with open(image_path, "rb") as f:
|
| 344 |
+
image_data = base64.b64encode(f.read()).decode("utf-8")
|
| 345 |
+
|
| 346 |
+
# Upload to ImgBB
|
| 347 |
+
response = requests.post(
|
| 348 |
+
"https://api.imgbb.com/1/upload",
|
| 349 |
+
data={
|
| 350 |
+
"key": IMGBB_KEY,
|
| 351 |
+
"image": image_data,
|
| 352 |
+
"expiration": 0, # Never expire
|
| 353 |
+
},
|
| 354 |
+
timeout=30,
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
response.raise_for_status()
|
| 358 |
+
result = response.json()
|
| 359 |
+
|
| 360 |
+
if not result.get("success"):
|
| 361 |
+
raise ValueError("ImgBB upload failed")
|
| 362 |
+
|
| 363 |
+
return result["data"]["url"]
|
| 364 |
+
|
| 365 |
+
except Exception as e:
|
| 366 |
+
raise HTTPException(
|
| 367 |
+
status_code=500,
|
| 368 |
+
detail=f"Image upload failed: {str(e)}"
|
| 369 |
+
)
|
| 370 |
+
finally:
|
| 371 |
+
# Clean up local file immediately after upload
|
| 372 |
+
try:
|
| 373 |
+
image_path.unlink()
|
| 374 |
+
except Exception:
|
| 375 |
+
pass
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 379 |
+
# API ENDPOINTS
|
| 380 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 381 |
+
|
| 382 |
+
@app.get("/")
|
| 383 |
+
async def root():
|
| 384 |
+
"""API Root - Health & Info"""
|
| 385 |
+
return {
|
| 386 |
+
"service": "VIBE_LINK API",
|
| 387 |
+
"status": "operational",
|
| 388 |
+
"version": "1.0.0",
|
| 389 |
+
"endpoints": {
|
| 390 |
+
"create": "POST /create - Generate vibe poster from URL",
|
| 391 |
+
"health": "GET /health - Service health check",
|
| 392 |
+
},
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
@app.get("/health")
|
| 397 |
+
async def health_check():
|
| 398 |
+
"""Health Check Endpoint (for Docker HEALTHCHECK)"""
|
| 399 |
+
return {"status": "healthy", "service": "vibe-link-backend"}
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
@app.post("/create", response_model=CreateResponse, responses={500: {"model": ErrorResponse}})
|
| 403 |
+
async def create_vibe_poster(request: CreateRequest):
|
| 404 |
+
"""
|
| 405 |
+
π¨ Main Endpoint: Generate Vibe Poster
|
| 406 |
+
|
| 407 |
+
Pipeline:
|
| 408 |
+
1. Capture screenshot of website
|
| 409 |
+
2. Analyze with Gemini AI
|
| 410 |
+
3. Generate poster with Flux.1
|
| 411 |
+
4. Upload to ImgBB
|
| 412 |
+
|
| 413 |
+
Returns:
|
| 414 |
+
CreateResponse with poster URL and metadata
|
| 415 |
+
"""
|
| 416 |
+
screenshot_path = None
|
| 417 |
+
|
| 418 |
+
try:
|
| 419 |
+
# STEP 1: Screenshot
|
| 420 |
+
print(f"πΈ Capturing screenshot: {request.url}")
|
| 421 |
+
screenshot_path = await capture_screenshot(str(request.url))
|
| 422 |
+
|
| 423 |
+
# STEP 2: AI Analysis
|
| 424 |
+
print("π§ Analyzing with Gemini...")
|
| 425 |
+
analysis = await analyze_with_gemini(screenshot_path)
|
| 426 |
+
|
| 427 |
+
# STEP 3: Generate Poster
|
| 428 |
+
print("π¨ Generating poster with Flux.1...")
|
| 429 |
+
poster_path = await generate_poster(analysis["flux_prompt"])
|
| 430 |
+
|
| 431 |
+
# STEP 4: Upload to ImgBB
|
| 432 |
+
print("βοΈ Uploading to ImgBB...")
|
| 433 |
+
poster_url = await upload_to_imgbb(poster_path)
|
| 434 |
+
|
| 435 |
+
print(f"β
Success! Poster URL: {poster_url}")
|
| 436 |
+
|
| 437 |
+
return CreateResponse(
|
| 438 |
+
status="success",
|
| 439 |
+
poster_url=poster_url,
|
| 440 |
+
vibe=analysis["vibe"],
|
| 441 |
+
summary=analysis["summary"],
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
except HTTPException:
|
| 445 |
+
raise
|
| 446 |
+
except Exception as e:
|
| 447 |
+
raise HTTPException(
|
| 448 |
+
status_code=500,
|
| 449 |
+
detail=f"Unexpected error: {str(e)}"
|
| 450 |
+
)
|
| 451 |
+
finally:
|
| 452 |
+
# Cleanup screenshot file
|
| 453 |
+
if screenshot_path and screenshot_path.exists():
|
| 454 |
+
try:
|
| 455 |
+
screenshot_path.unlink()
|
| 456 |
+
except Exception:
|
| 457 |
+
pass
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
@app.exception_handler(Exception)
|
| 461 |
+
async def global_exception_handler(request: Request, exc: Exception):
|
| 462 |
+
"""Global error handler for unhandled exceptions"""
|
| 463 |
+
return JSONResponse(
|
| 464 |
+
status_code=500,
|
| 465 |
+
content={
|
| 466 |
+
"status": "error",
|
| 467 |
+
"message": "Internal server error",
|
| 468 |
+
"detail": str(exc),
|
| 469 |
+
},
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 474 |
+
# MAIN ENTRY POINT
|
| 475 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 476 |
+
|
| 477 |
+
if __name__ == "__main__":
|
| 478 |
+
import uvicorn
|
| 479 |
+
|
| 480 |
+
uvicorn.run(
|
| 481 |
+
app,
|
| 482 |
+
host="0.0.0.0",
|
| 483 |
+
port=7860,
|
| 484 |
+
log_level="info",
|
| 485 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# VIBE_LINK Backend - Production Dependencies
|
| 2 |
+
# Optimized for minimal footprint and fast builds
|
| 3 |
+
|
| 4 |
+
# Core Framework
|
| 5 |
+
fastapi==0.109.0
|
| 6 |
+
uvicorn[standard]==0.27.0
|
| 7 |
+
python-multipart==0.0.6
|
| 8 |
+
|
| 9 |
+
# Headless Browser (Screenshot)
|
| 10 |
+
pyppeteer==1.0.2
|
| 11 |
+
|
| 12 |
+
# AI & Image Generation
|
| 13 |
+
google-generativeai==0.3.2
|
| 14 |
+
huggingface-hub==0.20.3
|
| 15 |
+
|
| 16 |
+
# Image Processing
|
| 17 |
+
Pillow==10.2.0
|
| 18 |
+
|
| 19 |
+
# HTTP & API Requests
|
| 20 |
+
requests==2.31.0
|
| 21 |
+
httpx==0.26.0
|
| 22 |
+
|
| 23 |
+
# Environment & Configuration
|
| 24 |
+
python-dotenv==1.0.1
|
| 25 |
+
|
| 26 |
+
# Async Support (optional optimization)
|
| 27 |
+
aiofiles==23.2.1
|