templ.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. {% load static %}
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>AI Background Remover</title>
  8. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
  9. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.3.0/styles/overlayscrollbars.min.css">
  10. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.min.css">
  11. <link rel="stylesheet" href="{% static './css/adminlte.css' %}">
  12. <script src="https://cdn.tailwindcss.com"></script>
  13. <style>
  14. .nav-pills .nav-link.active { background-color: #3b82f6 !important; }
  15. .checkerboard-bg {
  16. background-image: linear-gradient(45deg, #eee 25%, transparent 25%), linear-gradient(-45deg, #eee 25%, transparent 25%),
  17. linear-gradient(45deg, transparent 75%, #eee 75%), linear-gradient(-45deg, transparent 75%, #eee 75%);
  18. background-size: 20px 20px;
  19. background-color: white;
  20. }
  21. </style>
  22. </head>
  23. <body class="layout-fixed sidebar-expand-lg sidebar-mini app-loaded sidebar-collapse">
  24. <div id="bulk-loader" class="hidden fixed inset-0 bg-black/60 backdrop-blur-md z-[9999] flex flex-col items-center justify-center text-white">
  25. <div class="w-16 h-16 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin mb-4"></div>
  26. <h2 class="text-2xl font-bold">Processing Batch...</h2>
  27. </div>
  28. <div class="app-wrapper">
  29. {% include 'header.html' %}
  30. {% include 'sidebar.html' %}
  31. <main class="app-main">
  32. <div class="app-content pt-10">
  33. <div class="max-w-5xl mx-auto px-4">
  34. <ul class="nav nav-pills justify-content-center mb-8 gap-3" id="pills-tab" role="tablist">
  35. <li class="nav-item">
  36. <button class="nav-link active" id="pills-single-tab" data-bs-toggle="pill" data-bs-target="#pills-single" type="button">Single Image</button>
  37. </li>
  38. <li class="nav-item">
  39. <button class="nav-link" id="pills-bulk-tab" data-bs-toggle="pill" data-bs-target="#pills-bulk" type="button">Bulk Process</button>
  40. </li>
  41. </ul>
  42. <div class="tab-content">
  43. <div class="tab-pane fade show active" id="pills-single">
  44. <div id="single-upload-card" class="bg-white p-8 rounded-3xl shadow-sm border border-gray-200">
  45. <div class="border-2 border-dashed border-gray-300 flex flex-col items-center justify-center rounded-2xl p-12 cursor-pointer hover:border-blue-500 transition-all" onclick="document.getElementById('single-file-input').click()">
  46. <input type="file" id="single-file-input" class="hidden" accept="image/*">
  47. <i class="bi bi-image text-4xl text-blue-600 mb-4"></i>
  48. <p class="text-lg font-semibold text-gray-700">Upload a single image</p>
  49. </div>
  50. </div>
  51. <div id="single-result-area" class="hidden mt-8 space-y-6">
  52. <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  53. <div>
  54. <p class="text-xs font-bold text-gray-400 uppercase mb-2">Original</p>
  55. <img id="single-orig-prev" class="rounded-xl w-full border bg-white p-1">
  56. </div>
  57. <div>
  58. <p class="text-xs font-bold text-gray-400 uppercase mb-2">Result</p>
  59. <div class="checkerboard-bg rounded-xl border p-1 relative min-h-[200px] flex items-center justify-center">
  60. <div id="single-item-loader" class="hidden animate-spin w-10 h-10 border-4 border-blue-500 border-t-transparent rounded-full"></div>
  61. <img id="single-proc-prev" class="rounded-xl w-full">
  62. </div>
  63. </div>
  64. </div>
  65. <div class="flex justify-center gap-4">
  66. <button onclick="location.reload()" class="px-6 py-2 bg-gray-100 rounded-xl font-bold">Try Another</button>
  67. <a id="single-download-btn" class="hidden px-10 py-2 bg-green-600 text-white rounded-xl font-bold shadow-lg">Download PNG</a>
  68. </div>
  69. </div>
  70. </div>
  71. <div class="tab-pane fade" id="pills-bulk">
  72. <div id="bulk-container" class="bg-white p-8 rounded-3xl shadow-sm border border-gray-200">
  73. <div class="border-2 border-dashed border-gray-300 flex flex-col items-center justify-center rounded-2xl p-12 cursor-pointer hover:border-indigo-500" onclick="document.getElementById('bulk-file-input').click()">
  74. <input type="file" id="bulk-file-input" class="hidden" accept="image/*" multiple>
  75. <i class="bi bi-layers text-4xl text-indigo-600 mb-4"></i>
  76. <p class="text-lg font-semibold">Select Multiple Images</p>
  77. </div>
  78. <div id="bulk-queue-section" class="hidden mt-6">
  79. <div class="flex justify-between items-center mb-4">
  80. <span class="font-bold text-gray-700"><span id="bulk-count">0</span> files selected</span>
  81. <button id="start-bulk-btn" class="bg-indigo-600 text-white px-6 py-2 rounded-lg font-bold">Process & Zip</button>
  82. </div>
  83. <div id="bulk-preview-grid" class="grid grid-cols-4 md:grid-cols-6 gap-2"></div>
  84. </div>
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. </div>
  90. </main>
  91. </div>
  92. <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  93. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
  94. <script src="https://cdn.jsdelivr.net/npm/overlayscrollbars@2.3.0/browser/overlayscrollbars.browser.es6.min.js"></script>
  95. <script src="{% static './js/adminlte.js' %}"></script>
  96. <script>
  97. // --- SINGLE IMAGE LOGIC ---
  98. const singleInput = document.getElementById('single-file-input');
  99. singleInput.onchange = async (e) => {
  100. const file = e.target.files[0];
  101. if (!file) return;
  102. // Show UI
  103. document.getElementById('single-upload-card').classList.add('hidden');
  104. document.getElementById('single-result-area').classList.remove('hidden');
  105. document.getElementById('single-item-loader').classList.remove('hidden');
  106. document.getElementById('single-orig-prev').src = URL.createObjectURL(file);
  107. const formData = new FormData();
  108. formData.append('image', file);
  109. try {
  110. const response = await fetch('/bg-remover/remove-bg/single/', {
  111. method: 'POST',
  112. body: formData,
  113. headers: {'X-CSRFToken': '{{ csrf_token }}'}
  114. });
  115. if (response.ok) {
  116. const blob = await response.blob();
  117. const url = URL.createObjectURL(blob);
  118. document.getElementById('single-proc-prev').src = url;
  119. const dlBtn = document.getElementById('single-download-btn');
  120. dlBtn.href = url;
  121. dlBtn.download = "bg-removed.png";
  122. dlBtn.classList.remove('hidden');
  123. }
  124. } catch (err) { alert("Error processing image."); }
  125. finally { document.getElementById('single-item-loader').classList.add('hidden'); }
  126. };
  127. // --- BULK IMAGE LOGIC ---
  128. const bulkInput = document.getElementById('bulk-file-input');
  129. bulkInput.onchange = (e) => {
  130. const files = Array.from(e.target.files);
  131. document.getElementById('bulk-queue-section').classList.remove('hidden');
  132. document.getElementById('bulk-count').innerText = files.length;
  133. const grid = document.getElementById('bulk-preview-grid');
  134. grid.innerHTML = '';
  135. files.slice(0, 12).forEach(f => {
  136. const img = document.createElement('div');
  137. img.className = "h-12 bg-gray-100 rounded border text-[8px] overflow-hidden p-1";
  138. img.innerText = f.name;
  139. grid.appendChild(img);
  140. });
  141. };
  142. document.getElementById('start-bulk-btn').onclick = async () => {
  143. const formData = new FormData();
  144. for(let f of bulkInput.files) { formData.append('images', f); }
  145. document.getElementById('bulk-loader').classList.remove('hidden');
  146. try {
  147. const res = await fetch('/bg-remover/remove-bg/bulk/', {
  148. method: 'POST', body: formData, headers: {'X-CSRFToken': '{{ csrf_token }}'}
  149. });
  150. if (res.ok) {
  151. const blob = await res.blob();
  152. const url = URL.createObjectURL(blob);
  153. document.getElementById('bulk-container').innerHTML = `
  154. <div class="text-center py-10">
  155. <i class="bi bi-check-circle-fill text-6xl text-green-500 mb-4"></i>
  156. <h2 class="text-2xl font-bold mb-6">ZIP File Ready</h2>
  157. <a href="${url}" download="processed.zip" class="bg-indigo-600 text-white px-10 py-4 rounded-xl font-bold">Download All Images</a>
  158. <button onclick="location.reload()" class="block mx-auto mt-6 text-gray-400 underline">Upload More</button>
  159. </div>`;
  160. }
  161. } finally { document.getElementById('bulk-loader').classList.add('hidden'); }
  162. };
  163. // AdminLTE Sidebar Scrollbar
  164. document.addEventListener("DOMContentLoaded", function () {
  165. const sidebarWrapper = document.querySelector(".sidebar-wrapper");
  166. if (sidebarWrapper && typeof OverlayScrollbarsGlobal?.OverlayScrollbars !== "undefined") {
  167. OverlayScrollbarsGlobal.OverlayScrollbars(sidebarWrapper, {
  168. scrollbars: { theme: "os-theme-light", autoHide: "leave", clickScroll: true }
  169. });
  170. }
  171. });
  172. </script>
  173. </body>
  174. </html>