Lcmind commited on
Commit
6849bff
Β·
0 Parent(s):

πŸš€ Production-ready backend with Flux.1-schnell optimization

Browse files
Files changed (6) hide show
  1. .env.example +14 -0
  2. .gitignore +58 -0
  3. Dockerfile +62 -0
  4. README.md +198 -0
  5. main.py +485 -0
  6. 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