Spaces:
Paused
Paused
Esmaill1 commited on
Commit ·
aa82c4b
1
Parent(s): ab467eb
UI: Switch to Arabic language
Browse files- DEPLOYMENT.md +91 -0
- app/static/app.js +12 -12
- app/static/style.css +6 -6
- app/templates/index.html +15 -15
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face Spaces Deployment Guide
|
| 2 |
+
|
| 3 |
+
This project is configured for deployment on **Hugging Face Spaces** using the **Docker SDK**.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
1. A [Hugging Face Account](https://huggingface.co/join).
|
| 8 |
+
2. [Git](https://git-scm.com/downloads) installed on your machine.
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## 1. Create a New Space
|
| 13 |
+
|
| 14 |
+
1. Go to [huggingface.co/new-space](https://huggingface.co/new-space).
|
| 15 |
+
2. **Space Name**: `rmbg-studio` (or your preferred name).
|
| 16 |
+
3. **License**: `MIT`.
|
| 17 |
+
4. **SDK**: Select **Docker** (This is crucial!).
|
| 18 |
+
5. **Hardware**: `CPU basic` (Free) is sufficient, though `CPU upgrade` or `T4 small` will be faster.
|
| 19 |
+
6. **Visibility**: Public or Private.
|
| 20 |
+
7. Click **Create Space**.
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 2. Connect and Push Code
|
| 25 |
+
|
| 26 |
+
Open your terminal in the project folder and run these commands (replace `YOUR_USERNAME` with your HF username):
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
# 1. Initialize Git (if not already done)
|
| 30 |
+
git init
|
| 31 |
+
|
| 32 |
+
# 2. Add the Hugging Face remote
|
| 33 |
+
git remote add origin https://huggingface.co/spaces/YOUR_USERNAME/rmbg-studio
|
| 34 |
+
|
| 35 |
+
# 3. Add all files
|
| 36 |
+
git add .
|
| 37 |
+
git add -f Dockerfile
|
| 38 |
+
|
| 39 |
+
# 4. Commit
|
| 40 |
+
git commit -m "Initial commit"
|
| 41 |
+
|
| 42 |
+
# 5. Push to deploy
|
| 43 |
+
git push -u origin main
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
**Note:** If you get an error about "unrelated histories", use `git push -f origin main` to force overwrite the initial empty repo.
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## 3. Important Configurations
|
| 51 |
+
|
| 52 |
+
### Binary Files (Images/Icons)
|
| 53 |
+
Hugging Face prohibits pushing large binary files (like images) directly to git without LFS. To avoid errors, this project's `.gitignore` excludes:
|
| 54 |
+
- `*.png`, `*.jpg`, `*.jpeg`
|
| 55 |
+
- `icon.png`
|
| 56 |
+
- `screenshots/`
|
| 57 |
+
|
| 58 |
+
If you *need* to upload these, you must set up [Git LFS](https://git-lfs.com/) first.
|
| 59 |
+
|
| 60 |
+
### Dependencies (`Dockerfile`)
|
| 61 |
+
The included `Dockerfile` is optimized for Spaces:
|
| 62 |
+
- Base Image: `python:3.10-slim`
|
| 63 |
+
- System Libs: `libgl1`, `libglib2.0-0` (required for OpenCV)
|
| 64 |
+
- User: Runs as non-root user `user` (ID 1000) for security.
|
| 65 |
+
- Output: Creates `/app/output_images` with correct permissions.
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## 4. Updates & Troubleshooting
|
| 70 |
+
|
| 71 |
+
### How to Update
|
| 72 |
+
Whenever you make code changes:
|
| 73 |
+
```bash
|
| 74 |
+
git add .
|
| 75 |
+
git commit -m "Describe your changes"
|
| 76 |
+
git push
|
| 77 |
+
```
|
| 78 |
+
The Space will automatically rebuild and restart.
|
| 79 |
+
|
| 80 |
+
### Common Errors
|
| 81 |
+
|
| 82 |
+
**Error: `libgl1-mesa-glx` not found**
|
| 83 |
+
- **Cause**: Deprecated package in newer Debian versions.
|
| 84 |
+
- **Fix**: Use `libgl1` instead. (Already fixed in current `Dockerfile`).
|
| 85 |
+
|
| 86 |
+
**Error: Push rejected (binary files)**
|
| 87 |
+
- **Cause**: Trying to upload images without LFS.
|
| 88 |
+
- **Fix**: Run `git rm --cached icon.png` (and other images) to stop tracking them, then commit and push.
|
| 89 |
+
|
| 90 |
+
**Error: Application Error / Build Failed**
|
| 91 |
+
- Go to your Space's **Logs** tab to see the detailed error message. most issues are missing `requirements.txt` packages or path issues.
|
app/static/app.js
CHANGED
|
@@ -53,7 +53,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 53 |
// Skip non-images
|
| 54 |
if (!file.type.startsWith('image/')) continue;
|
| 55 |
|
| 56 |
-
progressText.textContent = `
|
| 57 |
|
| 58 |
const formData = new FormData();
|
| 59 |
formData.append('images', file); // API expects list but we act like size 1 batch
|
|
@@ -70,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 70 |
}
|
| 71 |
} catch (error) {
|
| 72 |
console.error(`Error processing ${file.name}:`, error);
|
| 73 |
-
showToast(`
|
| 74 |
}
|
| 75 |
|
| 76 |
// Update Progress
|
|
@@ -79,11 +79,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 79 |
progressBar.style.width = `${percent}%`;
|
| 80 |
}
|
| 81 |
|
| 82 |
-
progressText.textContent = '
|
| 83 |
setTimeout(() => {
|
| 84 |
loadingContainer.style.display = 'none';
|
| 85 |
}, 1500);
|
| 86 |
-
showToast(`
|
| 87 |
}
|
| 88 |
|
| 89 |
function createResultCard(result) {
|
|
@@ -93,14 +93,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 93 |
card.innerHTML = `
|
| 94 |
<div class="result-image-wrapper">
|
| 95 |
<img src="${result.url}" alt="${result.original_name}">
|
| 96 |
-
<button class="delete-btn" title="
|
| 97 |
<i class="ph ph-x"></i>
|
| 98 |
</button>
|
| 99 |
</div>
|
| 100 |
<div class="result-footer">
|
| 101 |
<div class="file-name" title="${result.original_name}">${result.original_name}</div>
|
| 102 |
<a href="${result.url}" download="${result.filename}" class="btn btn-primary" style="padding: 0.4rem 0.8rem; font-size: 0.8rem;">
|
| 103 |
-
<i class="ph ph-download-simple"></i>
|
| 104 |
</a>
|
| 105 |
</div>
|
| 106 |
`;
|
|
@@ -122,7 +122,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 122 |
|
| 123 |
// Clear All Logic
|
| 124 |
clearAllBtn.addEventListener('click', () => {
|
| 125 |
-
if(confirm('
|
| 126 |
resultsGrid.innerHTML = '';
|
| 127 |
resultsHeader.style.display = 'none';
|
| 128 |
}
|
|
@@ -134,7 +134,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 134 |
if (cards.length === 0) return;
|
| 135 |
|
| 136 |
downloadAllBtn.disabled = true;
|
| 137 |
-
downloadAllBtn.innerHTML = '<div class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block"></div>
|
| 138 |
|
| 139 |
const filenames = Array.from(cards).map(card => card.dataset.filename);
|
| 140 |
|
|
@@ -155,16 +155,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 155 |
a.click();
|
| 156 |
window.URL.revokeObjectURL(url);
|
| 157 |
a.remove();
|
| 158 |
-
showToast('
|
| 159 |
} else {
|
| 160 |
-
showToast('
|
| 161 |
}
|
| 162 |
} catch (error) {
|
| 163 |
console.error(error);
|
| 164 |
-
showToast('
|
| 165 |
} finally {
|
| 166 |
downloadAllBtn.disabled = false;
|
| 167 |
-
downloadAllBtn.innerHTML = '<i class="ph ph-download-simple"></i>
|
| 168 |
}
|
| 169 |
});
|
| 170 |
|
|
|
|
| 53 |
// Skip non-images
|
| 54 |
if (!file.type.startsWith('image/')) continue;
|
| 55 |
|
| 56 |
+
progressText.textContent = `جاري المعالجة ${i + 1}/${totalFiles}: ${file.name}`;
|
| 57 |
|
| 58 |
const formData = new FormData();
|
| 59 |
formData.append('images', file); // API expects list but we act like size 1 batch
|
|
|
|
| 70 |
}
|
| 71 |
} catch (error) {
|
| 72 |
console.error(`Error processing ${file.name}:`, error);
|
| 73 |
+
showToast(`فشل معالجة ${file.name}`, 'error');
|
| 74 |
}
|
| 75 |
|
| 76 |
// Update Progress
|
|
|
|
| 79 |
progressBar.style.width = `${percent}%`;
|
| 80 |
}
|
| 81 |
|
| 82 |
+
progressText.textContent = 'اكتملت المعالجة!';
|
| 83 |
setTimeout(() => {
|
| 84 |
loadingContainer.style.display = 'none';
|
| 85 |
}, 1500);
|
| 86 |
+
showToast(`تمت معالجة ${processedCount} صورة!`);
|
| 87 |
}
|
| 88 |
|
| 89 |
function createResultCard(result) {
|
|
|
|
| 93 |
card.innerHTML = `
|
| 94 |
<div class="result-image-wrapper">
|
| 95 |
<img src="${result.url}" alt="${result.original_name}">
|
| 96 |
+
<button class="delete-btn" title="حذف الصورة">
|
| 97 |
<i class="ph ph-x"></i>
|
| 98 |
</button>
|
| 99 |
</div>
|
| 100 |
<div class="result-footer">
|
| 101 |
<div class="file-name" title="${result.original_name}">${result.original_name}</div>
|
| 102 |
<a href="${result.url}" download="${result.filename}" class="btn btn-primary" style="padding: 0.4rem 0.8rem; font-size: 0.8rem;">
|
| 103 |
+
<i class="ph ph-download-simple"></i> حفظ
|
| 104 |
</a>
|
| 105 |
</div>
|
| 106 |
`;
|
|
|
|
| 122 |
|
| 123 |
// Clear All Logic
|
| 124 |
clearAllBtn.addEventListener('click', () => {
|
| 125 |
+
if(confirm('هل أنت متأكد من مسح جميع الصور المعالجة؟')) {
|
| 126 |
resultsGrid.innerHTML = '';
|
| 127 |
resultsHeader.style.display = 'none';
|
| 128 |
}
|
|
|
|
| 134 |
if (cards.length === 0) return;
|
| 135 |
|
| 136 |
downloadAllBtn.disabled = true;
|
| 137 |
+
downloadAllBtn.innerHTML = '<div class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block"></div> جاري الضغط...';
|
| 138 |
|
| 139 |
const filenames = Array.from(cards).map(card => card.dataset.filename);
|
| 140 |
|
|
|
|
| 155 |
a.click();
|
| 156 |
window.URL.revokeObjectURL(url);
|
| 157 |
a.remove();
|
| 158 |
+
showToast('بدأ تحميل الملف المضغوط!');
|
| 159 |
} else {
|
| 160 |
+
showToast('فشل إنشاء الملف المضغوط', 'error');
|
| 161 |
}
|
| 162 |
} catch (error) {
|
| 163 |
console.error(error);
|
| 164 |
+
showToast('خطأ في الشبكة', 'error');
|
| 165 |
} finally {
|
| 166 |
downloadAllBtn.disabled = false;
|
| 167 |
+
downloadAllBtn.innerHTML = '<i class="ph ph-download-simple"></i> تحميل الكل (ZIP)';
|
| 168 |
}
|
| 169 |
});
|
| 170 |
|
app/static/style.css
CHANGED
|
@@ -11,7 +11,7 @@
|
|
| 11 |
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
|
| 12 |
--shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
|
| 13 |
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
|
| 14 |
-
--font-main: '
|
| 15 |
--radius: 12px;
|
| 16 |
--radius-lg: 16px;
|
| 17 |
}
|
|
@@ -39,11 +39,11 @@ body {
|
|
| 39 |
.sidebar {
|
| 40 |
width: 280px;
|
| 41 |
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
| 42 |
-
border-
|
| 43 |
display: flex;
|
| 44 |
flex-direction: column;
|
| 45 |
padding: 1.5rem;
|
| 46 |
-
box-shadow: 2px 0 10px rgba(0,0,0,0.03);
|
| 47 |
}
|
| 48 |
|
| 49 |
.logo {
|
|
@@ -86,7 +86,7 @@ body {
|
|
| 86 |
cursor: pointer;
|
| 87 |
border-radius: var(--radius);
|
| 88 |
transition: all 0.2s ease;
|
| 89 |
-
text-align:
|
| 90 |
}
|
| 91 |
|
| 92 |
.nav-item i {
|
|
@@ -380,7 +380,7 @@ body {
|
|
| 380 |
.delete-btn {
|
| 381 |
position: absolute;
|
| 382 |
top: 12px;
|
| 383 |
-
|
| 384 |
background: rgba(255, 255, 255, 0.95);
|
| 385 |
border: none;
|
| 386 |
color: #ef4444;
|
|
@@ -435,7 +435,7 @@ body {
|
|
| 435 |
.toast {
|
| 436 |
position: fixed;
|
| 437 |
bottom: 24px;
|
| 438 |
-
|
| 439 |
padding: 1rem 1.5rem;
|
| 440 |
border-radius: var(--radius);
|
| 441 |
box-shadow: var(--shadow-lg);
|
|
|
|
| 11 |
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
|
| 12 |
--shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
|
| 13 |
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
|
| 14 |
+
--font-main: 'Cairo', sans-serif;
|
| 15 |
--radius: 12px;
|
| 16 |
--radius-lg: 16px;
|
| 17 |
}
|
|
|
|
| 39 |
.sidebar {
|
| 40 |
width: 280px;
|
| 41 |
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
| 42 |
+
border-left: 1px solid var(--border); /* Changed from border-right */
|
| 43 |
display: flex;
|
| 44 |
flex-direction: column;
|
| 45 |
padding: 1.5rem;
|
| 46 |
+
box-shadow: -2px 0 10px rgba(0,0,0,0.03); /* Flipped shadow direction */
|
| 47 |
}
|
| 48 |
|
| 49 |
.logo {
|
|
|
|
| 86 |
cursor: pointer;
|
| 87 |
border-radius: var(--radius);
|
| 88 |
transition: all 0.2s ease;
|
| 89 |
+
text-align: right; /* Changed from left */
|
| 90 |
}
|
| 91 |
|
| 92 |
.nav-item i {
|
|
|
|
| 380 |
.delete-btn {
|
| 381 |
position: absolute;
|
| 382 |
top: 12px;
|
| 383 |
+
left: 12px; /* Changed from right to left for RTL */
|
| 384 |
background: rgba(255, 255, 255, 0.95);
|
| 385 |
border: none;
|
| 386 |
color: #ef4444;
|
|
|
|
| 435 |
.toast {
|
| 436 |
position: fixed;
|
| 437 |
bottom: 24px;
|
| 438 |
+
left: 24px; /* Changed from right to left */
|
| 439 |
padding: 1rem 1.5rem;
|
| 440 |
border-radius: var(--radius);
|
| 441 |
box-shadow: var(--shadow-lg);
|
app/templates/index.html
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>RMBG-2 Studio |
|
| 7 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 8 |
<!-- Phosphor Icons -->
|
| 9 |
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
| 10 |
-
<!-- Google Fonts -->
|
| 11 |
-
<link href="https://fonts.googleapis.com/css2?family=
|
| 12 |
</head>
|
| 13 |
<body>
|
| 14 |
<div class="app-container">
|
|
@@ -22,14 +22,14 @@
|
|
| 22 |
<nav class="nav-menu">
|
| 23 |
<button class="nav-item active">
|
| 24 |
<i class="ph ph-eraser"></i>
|
| 25 |
-
|
| 26 |
</button>
|
| 27 |
</nav>
|
| 28 |
|
| 29 |
<div class="sidebar-footer">
|
| 30 |
<div class="status-indicator" id="status-indicator">
|
| 31 |
<span class="dot"></span>
|
| 32 |
-
<span class="text">
|
| 33 |
</div>
|
| 34 |
</div>
|
| 35 |
</aside>
|
|
@@ -37,8 +37,8 @@
|
|
| 37 |
<!-- Main Content -->
|
| 38 |
<main class="main-content">
|
| 39 |
<header class="section-header">
|
| 40 |
-
<h1>
|
| 41 |
-
<p>
|
| 42 |
</header>
|
| 43 |
|
| 44 |
<div class="workspace">
|
|
@@ -46,9 +46,9 @@
|
|
| 46 |
<input type="file" id="file-input" accept="image/*" multiple hidden>
|
| 47 |
<div class="upload-content">
|
| 48 |
<i class="ph ph-cloud-arrow-up"></i>
|
| 49 |
-
<h3>
|
| 50 |
-
<p>
|
| 51 |
-
<span class="supported-formats">JPG, PNG, WEBP (
|
| 52 |
</div>
|
| 53 |
</div>
|
| 54 |
|
|
@@ -56,17 +56,17 @@
|
|
| 56 |
<div class="loading-bar">
|
| 57 |
<div class="progress" id="progress-bar"></div>
|
| 58 |
</div>
|
| 59 |
-
<span id="progress-text">
|
| 60 |
</div>
|
| 61 |
|
| 62 |
<div class="results-header" id="results-header" style="display: none;">
|
| 63 |
-
<h2>
|
| 64 |
<div class="results-actions">
|
| 65 |
<button id="download-all-btn" class="btn btn-primary">
|
| 66 |
-
<i class="ph ph-download-simple"></i>
|
| 67 |
</button>
|
| 68 |
<button id="clear-all-btn" class="btn btn-secondary">
|
| 69 |
-
<i class="ph ph-trash"></i>
|
| 70 |
</button>
|
| 71 |
</div>
|
| 72 |
</div>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
+
<html lang="ar" dir="rtl">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>RMBG-2 Studio | إزالة الخلفية بالذكاء الاصطناعي</title>
|
| 7 |
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 8 |
<!-- Phosphor Icons -->
|
| 9 |
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
| 10 |
+
<!-- Google Fonts: Cairo -->
|
| 11 |
+
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
| 12 |
</head>
|
| 13 |
<body>
|
| 14 |
<div class="app-container">
|
|
|
|
| 22 |
<nav class="nav-menu">
|
| 23 |
<button class="nav-item active">
|
| 24 |
<i class="ph ph-eraser"></i>
|
| 25 |
+
إزالة الخلفية
|
| 26 |
</button>
|
| 27 |
</nav>
|
| 28 |
|
| 29 |
<div class="sidebar-footer">
|
| 30 |
<div class="status-indicator" id="status-indicator">
|
| 31 |
<span class="dot"></span>
|
| 32 |
+
<span class="text">جاهز</span>
|
| 33 |
</div>
|
| 34 |
</div>
|
| 35 |
</aside>
|
|
|
|
| 37 |
<!-- Main Content -->
|
| 38 |
<main class="main-content">
|
| 39 |
<header class="section-header">
|
| 40 |
+
<h1>إزالة الخلفية</h1>
|
| 41 |
+
<p>قم برفع صورة أو أكثر لإزالة الخلفية فوراً.</p>
|
| 42 |
</header>
|
| 43 |
|
| 44 |
<div class="workspace">
|
|
|
|
| 46 |
<input type="file" id="file-input" accept="image/*" multiple hidden>
|
| 47 |
<div class="upload-content">
|
| 48 |
<i class="ph ph-cloud-arrow-up"></i>
|
| 49 |
+
<h3>رفع الصور</h3>
|
| 50 |
+
<p>اسحب وأفلت الملفات هنا</p>
|
| 51 |
+
<span class="supported-formats">JPG, PNG, WEBP (بحد أقصى 50 ميجابايت)</span>
|
| 52 |
</div>
|
| 53 |
</div>
|
| 54 |
|
|
|
|
| 56 |
<div class="loading-bar">
|
| 57 |
<div class="progress" id="progress-bar"></div>
|
| 58 |
</div>
|
| 59 |
+
<span id="progress-text">جاري المعالجة...</span>
|
| 60 |
</div>
|
| 61 |
|
| 62 |
<div class="results-header" id="results-header" style="display: none;">
|
| 63 |
+
<h2>الصور المعالجة</h2>
|
| 64 |
<div class="results-actions">
|
| 65 |
<button id="download-all-btn" class="btn btn-primary">
|
| 66 |
+
<i class="ph ph-download-simple"></i> تحميل الكل (ZIP)
|
| 67 |
</button>
|
| 68 |
<button id="clear-all-btn" class="btn btn-secondary">
|
| 69 |
+
<i class="ph ph-trash"></i> مسح الكل
|
| 70 |
</button>
|
| 71 |
</div>
|
| 72 |
</div>
|