Deepfake Authenticator commited on
Commit ·
1dfcfc7
1
Parent(s): 70348ce
feat: split backend (HF) and frontend (Vercel)
Browse files- Backend: remove static file serving, API-only
- Backend: CORS updated to allow Vercel domains
- Dockerfile: remove frontend copy, pure API image
- Frontend: VITE_API_URL env var for HF Space URL
- Frontend: vercel.json with SPA rewrite rules
- Frontend: .env.example with setup instructions
- .gitignore +1 -0
- Dockerfile +2 -3
- backend/main.py +8 -62
- frontend-react/.env.example +3 -0
- frontend-react/src/App.tsx +2 -1
- frontend-react/src/api.ts +4 -0
- frontend-react/src/components/Navbar.tsx +3 -1
- frontend-react/vercel.json +8 -0
.gitignore
CHANGED
|
@@ -68,3 +68,4 @@ logs/
|
|
| 68 |
node_modules/
|
| 69 |
frontend-react/dist/
|
| 70 |
frontend-react/.vite/
|
|
|
|
|
|
| 68 |
node_modules/
|
| 69 |
frontend-react/dist/
|
| 70 |
frontend-react/.vite/
|
| 71 |
+
frontend-react/.env.local
|
Dockerfile
CHANGED
|
@@ -20,15 +20,14 @@ ENV HOME=/home/user \
|
|
| 20 |
|
| 21 |
WORKDIR /home/user/app
|
| 22 |
|
| 23 |
-
# Copy
|
| 24 |
COPY --chown=user backend/ ./backend/
|
| 25 |
-
COPY --chown=user frontend-vanilla/ ./frontend-vanilla/
|
| 26 |
|
| 27 |
# Install dependencies
|
| 28 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 29 |
pip install --no-cache-dir -r backend/requirements.txt
|
| 30 |
|
| 31 |
-
# Pre-cache HuggingFace models
|
| 32 |
RUN python -c "\
|
| 33 |
from transformers import ViTForImageClassification, ViTImageProcessor; \
|
| 34 |
ViTImageProcessor.from_pretrained('dima806/deepfake_vs_real_image_detection'); \
|
|
|
|
| 20 |
|
| 21 |
WORKDIR /home/user/app
|
| 22 |
|
| 23 |
+
# Copy only the backend
|
| 24 |
COPY --chown=user backend/ ./backend/
|
|
|
|
| 25 |
|
| 26 |
# Install dependencies
|
| 27 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 28 |
pip install --no-cache-dir -r backend/requirements.txt
|
| 29 |
|
| 30 |
+
# Pre-cache HuggingFace models at build time
|
| 31 |
RUN python -c "\
|
| 32 |
from transformers import ViTForImageClassification, ViTImageProcessor; \
|
| 33 |
ViTImageProcessor.from_pretrained('dima806/deepfake_vs_real_image_detection'); \
|
backend/main.py
CHANGED
|
@@ -11,8 +11,6 @@ from pathlib import Path
|
|
| 11 |
|
| 12 |
from fastapi import FastAPI, File, UploadFile, HTTPException, Header
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
-
from fastapi.staticfiles import StaticFiles
|
| 15 |
-
from fastapi.responses import FileResponse
|
| 16 |
from typing import Optional
|
| 17 |
|
| 18 |
# ── Logging (must be set up before any logger usage) ─────────────────────────
|
|
@@ -94,7 +92,14 @@ app = FastAPI(
|
|
| 94 |
|
| 95 |
app.add_middleware(
|
| 96 |
CORSMiddleware,
|
| 97 |
-
allow_origins=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
allow_methods=["*"],
|
| 99 |
allow_headers=["*"],
|
| 100 |
)
|
|
@@ -114,7 +119,6 @@ authenticator = None
|
|
| 114 |
async def startup_event():
|
| 115 |
global authenticator
|
| 116 |
logger.info("Initializing DeepfakeAuthenticator...")
|
| 117 |
-
logger.info(f"Frontend path: {_vanilla_dir} (exists={_vanilla_dir.exists()})")
|
| 118 |
authenticator = DeepfakeAuthenticator()
|
| 119 |
logger.info(
|
| 120 |
f"DeepfakeAuthenticator ready — model: "
|
|
@@ -343,64 +347,6 @@ async def analyze_video(
|
|
| 343 |
pass
|
| 344 |
|
| 345 |
|
| 346 |
-
# ── Serve frontend ────────────────────────────
|
| 347 |
-
_react_dist = Path(__file__).parent.parent / "frontend-dist"
|
| 348 |
-
_vanilla_dir = Path(__file__).parent.parent / "frontend-vanilla"
|
| 349 |
-
|
| 350 |
-
if _react_dist.exists():
|
| 351 |
-
app.mount("/assets", StaticFiles(directory=str(_react_dist / "assets")), name="assets")
|
| 352 |
-
|
| 353 |
-
@app.get("/")
|
| 354 |
-
async def serve_index():
|
| 355 |
-
return FileResponse(
|
| 356 |
-
str(_react_dist / "index.html"),
|
| 357 |
-
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 358 |
-
)
|
| 359 |
-
|
| 360 |
-
@app.get("/{full_path:path}")
|
| 361 |
-
async def spa_fallback(full_path: str):
|
| 362 |
-
index = _react_dist / "index.html"
|
| 363 |
-
if index.exists():
|
| 364 |
-
return FileResponse(str(index))
|
| 365 |
-
return {"detail": "Not found"}
|
| 366 |
-
|
| 367 |
-
elif _vanilla_dir.exists():
|
| 368 |
-
from fastapi.staticfiles import StaticFiles as SF
|
| 369 |
-
|
| 370 |
-
@app.get("/script.js")
|
| 371 |
-
async def serve_script():
|
| 372 |
-
return FileResponse(
|
| 373 |
-
str(_vanilla_dir / "script.js"),
|
| 374 |
-
media_type="application/javascript",
|
| 375 |
-
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 376 |
-
)
|
| 377 |
-
|
| 378 |
-
@app.get("/pricing")
|
| 379 |
-
async def serve_pricing():
|
| 380 |
-
return FileResponse(
|
| 381 |
-
str(_vanilla_dir / "pricing.html"),
|
| 382 |
-
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 383 |
-
)
|
| 384 |
-
|
| 385 |
-
@app.get("/")
|
| 386 |
-
async def serve_index():
|
| 387 |
-
return FileResponse(
|
| 388 |
-
str(_vanilla_dir / "index.html"),
|
| 389 |
-
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 390 |
-
)
|
| 391 |
-
|
| 392 |
-
# Serve any other static file from frontend-vanilla/
|
| 393 |
-
@app.get("/{filename:path}")
|
| 394 |
-
async def serve_static(filename: str):
|
| 395 |
-
file_path = _vanilla_dir / filename
|
| 396 |
-
if file_path.exists() and file_path.is_file():
|
| 397 |
-
return FileResponse(str(file_path))
|
| 398 |
-
return FileResponse(
|
| 399 |
-
str(_vanilla_dir / "index.html"),
|
| 400 |
-
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 401 |
-
)
|
| 402 |
-
|
| 403 |
-
|
| 404 |
if __name__ == "__main__":
|
| 405 |
import uvicorn
|
| 406 |
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=False)
|
|
|
|
| 11 |
|
| 12 |
from fastapi import FastAPI, File, UploadFile, HTTPException, Header
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
| 14 |
from typing import Optional
|
| 15 |
|
| 16 |
# ── Logging (must be set up before any logger usage) ─────────────────────────
|
|
|
|
| 92 |
|
| 93 |
app.add_middleware(
|
| 94 |
CORSMiddleware,
|
| 95 |
+
allow_origins=[
|
| 96 |
+
"http://localhost:5173",
|
| 97 |
+
"http://localhost:3000",
|
| 98 |
+
"https://*.vercel.app",
|
| 99 |
+
"https://authrix.vercel.app",
|
| 100 |
+
# Add your custom domain here if you have one
|
| 101 |
+
],
|
| 102 |
+
allow_origin_regex=r"https://.*\.vercel\.app",
|
| 103 |
allow_methods=["*"],
|
| 104 |
allow_headers=["*"],
|
| 105 |
)
|
|
|
|
| 119 |
async def startup_event():
|
| 120 |
global authenticator
|
| 121 |
logger.info("Initializing DeepfakeAuthenticator...")
|
|
|
|
| 122 |
authenticator = DeepfakeAuthenticator()
|
| 123 |
logger.info(
|
| 124 |
f"DeepfakeAuthenticator ready — model: "
|
|
|
|
| 347 |
pass
|
| 348 |
|
| 349 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
if __name__ == "__main__":
|
| 351 |
import uvicorn
|
| 352 |
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=False)
|
frontend-react/.env.example
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copy to .env.local for local dev (optional — dev proxy handles it automatically)
|
| 2 |
+
# Set this in Vercel dashboard: Settings → Environment Variables
|
| 3 |
+
VITE_API_URL=https://aarav13-authrix.hf.space
|
frontend-react/src/App.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import type { AnalysisResult, AppState } from './types';
|
|
|
|
| 3 |
import Background from './components/Background';
|
| 4 |
import RadioNav from './components/RadioNav';
|
| 5 |
import HeroSection from './components/HeroSection';
|
|
@@ -29,7 +30,7 @@ export default function App() {
|
|
| 29 |
const fd = new FormData();
|
| 30 |
fd.append('file', file);
|
| 31 |
try {
|
| 32 |
-
const res = await fetch(
|
| 33 |
if (!res.ok) {
|
| 34 |
const e = await res.json().catch(() => ({}));
|
| 35 |
throw new Error((e as { detail?: string }).detail || `Server error ${res.status}`);
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import type { AnalysisResult, AppState } from './types';
|
| 3 |
+
import { API_BASE } from './api';
|
| 4 |
import Background from './components/Background';
|
| 5 |
import RadioNav from './components/RadioNav';
|
| 6 |
import HeroSection from './components/HeroSection';
|
|
|
|
| 30 |
const fd = new FormData();
|
| 31 |
fd.append('file', file);
|
| 32 |
try {
|
| 33 |
+
const res = await fetch(`${API_BASE}/analyze`, { method: 'POST', body: fd });
|
| 34 |
if (!res.ok) {
|
| 35 |
const e = await res.json().catch(() => ({}));
|
| 36 |
throw new Error((e as { detail?: string }).detail || `Server error ${res.status}`);
|
frontend-react/src/api.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// In dev: uses Vite proxy (localhost:8000)
|
| 2 |
+
// In production (Vercel): uses VITE_API_URL env var pointing to HF Space
|
| 3 |
+
export const API_BASE =
|
| 4 |
+
import.meta.env.VITE_API_URL?.replace(/\/$/, '') ?? '';
|
frontend-react/src/components/Navbar.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import { useEffect, useState } from 'react';
|
| 2 |
|
|
|
|
|
|
|
| 3 |
interface NavbarProps {
|
| 4 |
onDashboard: () => void;
|
| 5 |
onGetStarted: () => void;
|
|
@@ -10,7 +12,7 @@ export default function Navbar({ onDashboard, onGetStarted, onOpenModal }: Navba
|
|
| 10 |
const [health, setHealth] = useState<'checking' | 'online' | 'offline'>('checking');
|
| 11 |
|
| 12 |
useEffect(() => {
|
| 13 |
-
fetch(
|
| 14 |
.then(r => r.ok ? setHealth('online') : setHealth('offline'))
|
| 15 |
.catch(() => setHealth('offline'));
|
| 16 |
}, []);
|
|
|
|
| 1 |
import { useEffect, useState } from 'react';
|
| 2 |
|
| 3 |
+
const API_BASE = import.meta.env.VITE_API_URL?.replace(/\/$/, '') ?? '';
|
| 4 |
+
|
| 5 |
interface NavbarProps {
|
| 6 |
onDashboard: () => void;
|
| 7 |
onGetStarted: () => void;
|
|
|
|
| 12 |
const [health, setHealth] = useState<'checking' | 'online' | 'offline'>('checking');
|
| 13 |
|
| 14 |
useEffect(() => {
|
| 15 |
+
fetch(`${API_BASE}/health`)
|
| 16 |
.then(r => r.ok ? setHealth('online') : setHealth('offline'))
|
| 17 |
.catch(() => setHealth('offline'));
|
| 18 |
}, []);
|
frontend-react/vercel.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"buildCommand": "npm run build",
|
| 3 |
+
"outputDirectory": "dist",
|
| 4 |
+
"framework": "vite",
|
| 5 |
+
"rewrites": [
|
| 6 |
+
{ "source": "/(.*)", "destination": "/index.html" }
|
| 7 |
+
]
|
| 8 |
+
}
|