Commit
·
0eb5f56
0
Parent(s):
update
Browse files- Dockerfile +47 -0
- README.md +69 -0
- app/server.py +62 -0
- pyproject.toml +19 -0
- templates/index.html +151 -0
Dockerfile
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10-slim
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
# Create non-root user
|
6 |
+
RUN useradd -m -u 1000 user
|
7 |
+
|
8 |
+
# Install system dependencies and Chrome for Playwright
|
9 |
+
RUN apt-get update && apt-get install -y \
|
10 |
+
wget \
|
11 |
+
gnupg \
|
12 |
+
curl \
|
13 |
+
&& pip install --upgrade pip \
|
14 |
+
&& pip install poetry
|
15 |
+
|
16 |
+
# Copy poetry configuration
|
17 |
+
COPY pyproject.toml poetry.lock* ./
|
18 |
+
|
19 |
+
# Install Python dependencies using Poetry
|
20 |
+
RUN poetry config virtualenvs.create false \
|
21 |
+
&& poetry install --no-interaction --no-ansi --only main
|
22 |
+
|
23 |
+
# Install Playwright browsers
|
24 |
+
RUN playwright install chromium && \
|
25 |
+
playwright install-deps chromium
|
26 |
+
|
27 |
+
# Create directories
|
28 |
+
RUN mkdir -p static templates screenshots && \
|
29 |
+
chown -R user:user /app
|
30 |
+
|
31 |
+
# Copy application code
|
32 |
+
COPY app /app/app
|
33 |
+
COPY templates /app/templates
|
34 |
+
COPY static /app/static
|
35 |
+
|
36 |
+
# Environment variables
|
37 |
+
ENV PORT=7860 \
|
38 |
+
HOST=0.0.0.0
|
39 |
+
|
40 |
+
# Switch to non-root user
|
41 |
+
USER user
|
42 |
+
|
43 |
+
# Expose the port
|
44 |
+
EXPOSE 7860
|
45 |
+
|
46 |
+
# Start command
|
47 |
+
CMD ["uvicorn", "app.server:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Minimal Browser Screenshot Experiment
|
2 |
+
|
3 |
+
Cette application web permet de prendre des captures d'écran de sites web en utilisant un navigateur headless et de les afficher directement dans l'interface utilisateur.
|
4 |
+
|
5 |
+
## Fonctionnalités
|
6 |
+
|
7 |
+
- Interface web simple et réactive
|
8 |
+
- Saisie d'URL avec valeur par défaut pointant vers le Open LLM Leaderboard
|
9 |
+
- Capture d'écran via Playwright (navigateur Chromium)
|
10 |
+
- Affichage du résultat dans l'interface
|
11 |
+
|
12 |
+
## Technologies utilisées
|
13 |
+
|
14 |
+
- **Backend**: FastAPI, Playwright
|
15 |
+
- **Frontend**: HTML/JS avec Tailwind CSS
|
16 |
+
- **Gestion des dépendances**: Poetry
|
17 |
+
- **Déploiement**: Docker, Hugging Face Spaces
|
18 |
+
|
19 |
+
## Installation locale
|
20 |
+
|
21 |
+
### Prérequis
|
22 |
+
|
23 |
+
- Python 3.10+
|
24 |
+
- Poetry
|
25 |
+
- Docker (optionnel)
|
26 |
+
|
27 |
+
### Installation avec Poetry
|
28 |
+
|
29 |
+
```bash
|
30 |
+
# Cloner le dépôt
|
31 |
+
git clone [url-du-repo]
|
32 |
+
cd minimal-browser-screenshot-experiment
|
33 |
+
|
34 |
+
# Installer les dépendances
|
35 |
+
poetry install
|
36 |
+
|
37 |
+
# Installer les navigateurs Playwright
|
38 |
+
poetry run playwright install chromium
|
39 |
+
poetry run playwright install-deps chromium
|
40 |
+
|
41 |
+
# Lancer l'application
|
42 |
+
poetry run uvicorn app.server:app --host 0.0.0.0 --port 7860
|
43 |
+
```
|
44 |
+
|
45 |
+
### Utilisation avec Docker
|
46 |
+
|
47 |
+
```bash
|
48 |
+
# Construire l'image Docker
|
49 |
+
docker build -t minimal-browser-screenshot .
|
50 |
+
|
51 |
+
# Lancer le conteneur
|
52 |
+
docker run -p 7860:7860 minimal-browser-screenshot
|
53 |
+
```
|
54 |
+
|
55 |
+
## Déploiement sur Hugging Face Spaces
|
56 |
+
|
57 |
+
Cette application est conçue pour être facilement déployée sur un Hugging Face Space.
|
58 |
+
|
59 |
+
1. Créez un nouveau Space de type Docker
|
60 |
+
2. Associez ce dépôt GitHub à votre Space
|
61 |
+
3. Le Space utilisera automatiquement le Dockerfile fourni pour construire et déployer l'application
|
62 |
+
|
63 |
+
## Utilisation
|
64 |
+
|
65 |
+
1. Accédez à l'application via votre navigateur
|
66 |
+
2. Entrez l'URL du site dont vous souhaitez prendre une capture d'écran (par défaut: Open LLM Leaderboard)
|
67 |
+
3. Cliquez sur "Prendre une capture d'écran"
|
68 |
+
4. Attendez quelques secondes pour que le navigateur headless charge la page et prenne la capture
|
69 |
+
5. La capture d'écran s'affiche dans l'interface
|
app/server.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import asyncio
|
3 |
+
from fastapi import FastAPI, Request, Form
|
4 |
+
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
|
5 |
+
from fastapi.staticfiles import StaticFiles
|
6 |
+
from fastapi.templating import Jinja2Templates
|
7 |
+
from playwright.async_api import async_playwright
|
8 |
+
import uuid
|
9 |
+
import logging
|
10 |
+
|
11 |
+
# Configure logging
|
12 |
+
logging.basicConfig(level=logging.INFO)
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
+
|
15 |
+
# Create FastAPI app
|
16 |
+
app = FastAPI()
|
17 |
+
|
18 |
+
# Set up templates and static files
|
19 |
+
templates = Jinja2Templates(directory="templates")
|
20 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
21 |
+
os.makedirs("screenshots", exist_ok=True)
|
22 |
+
|
23 |
+
# Mount the screenshots directory
|
24 |
+
app.mount("/screenshots", StaticFiles(directory="screenshots"), name="screenshots")
|
25 |
+
|
26 |
+
@app.get("/", response_class=HTMLResponse)
|
27 |
+
async def read_root(request: Request):
|
28 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
29 |
+
|
30 |
+
@app.post("/take-screenshot")
|
31 |
+
async def take_screenshot(url: str = Form(...)):
|
32 |
+
logger.info(f"Taking screenshot of URL: {url}")
|
33 |
+
|
34 |
+
try:
|
35 |
+
# Generate a unique filename
|
36 |
+
filename = f"{uuid.uuid4()}.png"
|
37 |
+
filepath = f"screenshots/{filename}"
|
38 |
+
|
39 |
+
# Take the screenshot with Playwright
|
40 |
+
async with async_playwright() as p:
|
41 |
+
browser = await p.chromium.launch()
|
42 |
+
page = await browser.new_page()
|
43 |
+
await page.goto(url, wait_until="networkidle")
|
44 |
+
await page.screenshot(path=filepath)
|
45 |
+
await browser.close()
|
46 |
+
|
47 |
+
return JSONResponse({
|
48 |
+
"success": True,
|
49 |
+
"screenshot_url": f"/screenshots/{filename}"
|
50 |
+
})
|
51 |
+
|
52 |
+
except Exception as e:
|
53 |
+
logger.error(f"Error taking screenshot: {str(e)}")
|
54 |
+
return JSONResponse({
|
55 |
+
"success": False,
|
56 |
+
"error": str(e)
|
57 |
+
}, status_code=500)
|
58 |
+
|
59 |
+
# Add a health check endpoint
|
60 |
+
@app.get("/health")
|
61 |
+
async def health_check():
|
62 |
+
return {"status": "ok"}
|
pyproject.toml
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.poetry]
|
2 |
+
name = "minimal-browser-screenshot"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = "Application qui prend des captures d'écran de sites web via un navigateur"
|
5 |
+
authors = ["HuggingFace Team"]
|
6 |
+
readme = "README.md"
|
7 |
+
|
8 |
+
[tool.poetry.dependencies]
|
9 |
+
python = "^3.10"
|
10 |
+
fastapi = "^0.103.1"
|
11 |
+
uvicorn = "^0.23.2"
|
12 |
+
jinja2 = "^3.1.2"
|
13 |
+
python-multipart = "^0.0.6"
|
14 |
+
playwright = "^1.39.0"
|
15 |
+
aiofiles = "^23.2.1"
|
16 |
+
|
17 |
+
[build-system]
|
18 |
+
requires = ["poetry-core"]
|
19 |
+
build-backend = "poetry.core.masonry.api"
|
templates/index.html
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="fr">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>Capture d'écran du navigateur</title>
|
7 |
+
<link
|
8 |
+
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
|
9 |
+
rel="stylesheet"
|
10 |
+
/>
|
11 |
+
<style>
|
12 |
+
.loading {
|
13 |
+
display: none;
|
14 |
+
}
|
15 |
+
.screenshot-container {
|
16 |
+
max-width: 100%;
|
17 |
+
overflow-x: auto;
|
18 |
+
}
|
19 |
+
.screenshot-image {
|
20 |
+
max-width: 100%;
|
21 |
+
}
|
22 |
+
</style>
|
23 |
+
</head>
|
24 |
+
<body class="bg-gray-100 min-h-screen">
|
25 |
+
<div class="container mx-auto px-4 py-8">
|
26 |
+
<h1 class="text-3xl font-bold text-center mb-8">
|
27 |
+
Capture d'écran du navigateur
|
28 |
+
</h1>
|
29 |
+
|
30 |
+
<div class="bg-white p-6 rounded-lg shadow-md">
|
31 |
+
<form id="screenshot-form" class="mb-4">
|
32 |
+
<div class="mb-4">
|
33 |
+
<label for="url" class="block text-gray-700 font-medium mb-2"
|
34 |
+
>URL à capturer</label
|
35 |
+
>
|
36 |
+
<input
|
37 |
+
type="url"
|
38 |
+
id="url"
|
39 |
+
name="url"
|
40 |
+
value="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard"
|
41 |
+
class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
42 |
+
required
|
43 |
+
/>
|
44 |
+
</div>
|
45 |
+
<button
|
46 |
+
type="submit"
|
47 |
+
class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg"
|
48 |
+
>
|
49 |
+
Prendre une capture d'écran
|
50 |
+
</button>
|
51 |
+
</form>
|
52 |
+
|
53 |
+
<div id="loading" class="loading flex justify-center items-center py-8">
|
54 |
+
<svg
|
55 |
+
class="animate-spin -ml-1 mr-3 h-8 w-8 text-blue-500"
|
56 |
+
xmlns="http://www.w3.org/2000/svg"
|
57 |
+
fill="none"
|
58 |
+
viewBox="0 0 24 24"
|
59 |
+
>
|
60 |
+
<circle
|
61 |
+
class="opacity-25"
|
62 |
+
cx="12"
|
63 |
+
cy="12"
|
64 |
+
r="10"
|
65 |
+
stroke="currentColor"
|
66 |
+
stroke-width="4"
|
67 |
+
></circle>
|
68 |
+
<path
|
69 |
+
class="opacity-75"
|
70 |
+
fill="currentColor"
|
71 |
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
72 |
+
></path>
|
73 |
+
</svg>
|
74 |
+
<span class="text-lg text-gray-700">Capture d'écran en cours...</span>
|
75 |
+
</div>
|
76 |
+
|
77 |
+
<div
|
78 |
+
id="error"
|
79 |
+
class="hidden bg-red-100 text-red-700 p-4 rounded-lg mt-4"
|
80 |
+
></div>
|
81 |
+
|
82 |
+
<div id="screenshot" class="hidden mt-6">
|
83 |
+
<h2 class="text-xl font-semibold mb-4">Résultat :</h2>
|
84 |
+
<div class="screenshot-container border rounded-lg p-2">
|
85 |
+
<img
|
86 |
+
id="screenshot-image"
|
87 |
+
class="screenshot-image mx-auto"
|
88 |
+
src=""
|
89 |
+
alt="Capture d'écran"
|
90 |
+
/>
|
91 |
+
</div>
|
92 |
+
</div>
|
93 |
+
</div>
|
94 |
+
</div>
|
95 |
+
|
96 |
+
<script>
|
97 |
+
document
|
98 |
+
.getElementById("screenshot-form")
|
99 |
+
.addEventListener("submit", async function (e) {
|
100 |
+
e.preventDefault();
|
101 |
+
|
102 |
+
const url = document.getElementById("url").value;
|
103 |
+
const loadingEl = document.getElementById("loading");
|
104 |
+
const errorEl = document.getElementById("error");
|
105 |
+
const screenshotEl = document.getElementById("screenshot");
|
106 |
+
const screenshotImageEl = document.getElementById("screenshot-image");
|
107 |
+
|
108 |
+
// Reset UI
|
109 |
+
errorEl.classList.add("hidden");
|
110 |
+
errorEl.textContent = "";
|
111 |
+
screenshotEl.classList.add("hidden");
|
112 |
+
loadingEl.style.display = "flex";
|
113 |
+
|
114 |
+
try {
|
115 |
+
// Create form data
|
116 |
+
const formData = new FormData();
|
117 |
+
formData.append("url", url);
|
118 |
+
|
119 |
+
// Send request
|
120 |
+
const response = await fetch("/take-screenshot", {
|
121 |
+
method: "POST",
|
122 |
+
body: formData,
|
123 |
+
});
|
124 |
+
|
125 |
+
const data = await response.json();
|
126 |
+
|
127 |
+
if (response.ok && data.success) {
|
128 |
+
// Show screenshot
|
129 |
+
screenshotImageEl.src = data.screenshot_url;
|
130 |
+
screenshotEl.classList.remove("hidden");
|
131 |
+
} else {
|
132 |
+
// Show error
|
133 |
+
errorEl.textContent =
|
134 |
+
data.error ||
|
135 |
+
"Une erreur est survenue lors de la capture d'écran.";
|
136 |
+
errorEl.classList.remove("hidden");
|
137 |
+
}
|
138 |
+
} catch (error) {
|
139 |
+
// Show error
|
140 |
+
errorEl.textContent =
|
141 |
+
"Une erreur est survenue lors de la connexion au serveur.";
|
142 |
+
errorEl.classList.remove("hidden");
|
143 |
+
console.error(error);
|
144 |
+
} finally {
|
145 |
+
// Hide loading
|
146 |
+
loadingEl.style.display = "none";
|
147 |
+
}
|
148 |
+
});
|
149 |
+
</script>
|
150 |
+
</body>
|
151 |
+
</html>
|