Esmaill1 commited on
Commit
08a45b9
·
1 Parent(s): 2faaae6

Optimize caching and further reduce asset sizes for better UI responsiveness

Browse files
Files changed (2) hide show
  1. web/server.py +8 -8
  2. web/web_storage/index.html +7 -13
web/server.py CHANGED
@@ -119,15 +119,15 @@ async def upload_image(file: UploadFile = File(...)):
119
  # Get original dimensions after transposition for the web cropper
120
  width, height = img.size
121
 
122
- # Create a faster, smaller thumbnail for the UI (400x400 is plenty for the queue)
123
- img.thumbnail((400, 400), Image.BILINEAR)
124
  thumb_path = UPLOAD_DIR / f"{file_id}_thumb.jpg"
125
  if img.mode in ("RGBA", "LA"):
126
  bg = Image.new("RGB", img.size, (255, 255, 255))
127
  bg.paste(img, mask=img.split()[-1])
128
- bg.save(thumb_path, "JPEG", quality=85)
129
  else:
130
- img.convert("RGB").save(thumb_path, "JPEG", quality=85)
131
  return {
132
  "id": file_id,
133
  "filename": file.filename,
@@ -225,15 +225,15 @@ async def process_image(
225
  result_path = RESULT_DIR / f"{file_id}_layout.jpg"
226
  final_layout.save(result_path, "JPEG", quality=95, dpi=(300, 300))
227
 
228
- # 5. Generate a lightweight WEB PREVIEW (max 1200px width) for the UI
229
  preview_path = RESULT_DIR / f"{file_id}_preview.jpg"
230
  pw, ph = final_layout.size
231
- p_scale = 1200 / pw if pw > 1200 else 1.0
232
  if p_scale < 1.0:
233
  preview_img = final_layout.resize((int(pw * p_scale), int(ph * p_scale)), Image.BILINEAR)
234
- preview_img.save(preview_path, "JPEG", quality=80)
235
  else:
236
- final_layout.save(preview_path, "JPEG", quality=80)
237
 
238
  if temp_crop.exists(): temp_crop.unlink()
239
 
 
119
  # Get original dimensions after transposition for the web cropper
120
  width, height = img.size
121
 
122
+ # Create a faster, smaller thumbnail for the UI (200x200 is plenty for the 72px grid)
123
+ img.thumbnail((200, 200), Image.BILINEAR)
124
  thumb_path = UPLOAD_DIR / f"{file_id}_thumb.jpg"
125
  if img.mode in ("RGBA", "LA"):
126
  bg = Image.new("RGB", img.size, (255, 255, 255))
127
  bg.paste(img, mask=img.split()[-1])
128
+ bg.save(thumb_path, "JPEG", quality=60)
129
  else:
130
+ img.convert("RGB").save(thumb_path, "JPEG", quality=60)
131
  return {
132
  "id": file_id,
133
  "filename": file.filename,
 
225
  result_path = RESULT_DIR / f"{file_id}_layout.jpg"
226
  final_layout.save(result_path, "JPEG", quality=95, dpi=(300, 300))
227
 
228
+ # 5. Generate a lightweight WEB PREVIEW (max 900px width) for the UI
229
  preview_path = RESULT_DIR / f"{file_id}_preview.jpg"
230
  pw, ph = final_layout.size
231
+ p_scale = 900 / pw if pw > 900 else 1.0
232
  if p_scale < 1.0:
233
  preview_img = final_layout.resize((int(pw * p_scale), int(ph * p_scale)), Image.BILINEAR)
234
+ preview_img.save(preview_path, "JPEG", quality=70)
235
  else:
236
+ final_layout.save(preview_path, "JPEG", quality=70)
237
 
238
  if temp_crop.exists(): temp_crop.unlink()
239
 
web/web_storage/index.html CHANGED
@@ -793,7 +793,7 @@
793
  for (let file of files) {
794
  try {
795
  const data = await uploadFileWithProgress(file);
796
- imageData.push({ ...data, name: "", id_num: "", result_url: null, custom_crop: null, steps: null, status: 'waiting' });
797
  renderQueue();
798
  if (currentIndex === -1) selectImage(imageData.length - 1);
799
  } catch (e) {
@@ -840,7 +840,6 @@
840
  return;
841
  }
842
 
843
- const t = new Date().getTime();
844
  const html = imageData.map((img, idx) => {
845
  let statusIcon = '';
846
  if (img.status === 'waiting') statusIcon = '<i class="fa-regular fa-clock text-slate-500"></i>';
@@ -850,7 +849,7 @@
850
 
851
  return `
852
  <div onclick="selectImage(${idx})" class="queue-slide ${currentIndex === idx ? 'active' : ''}">
853
- <img src="${img.preview_url ? img.preview_url + '?t=' + t : img.thumb_url}" alt="">
854
  <div class="slide-status">${statusIcon}</div>
855
  <button onclick="deleteImage(event, ${idx})" class="slide-delete" title="حذف"><i class="fa-solid fa-xmark"></i></button>
856
  <div class="slide-name">${img.filename}</div>
@@ -895,16 +894,10 @@
895
  reprocessBtn.disabled = !data.result_url;
896
 
897
  const previewSkeleton = document.getElementById('preview-skeleton');
898
- const url = data.preview_url ? data.preview_url + '?t=' + Date.now() :
899
- data.result_url ? data.result_url + '?t=' + Date.now() :
900
  data.thumb_url;
901
 
902
- // Reset the image completely before loading the new one
903
- mainPreview.classList.add('hidden');
904
- mainPreview.classList.remove('opacity-100');
905
- mainPreview.classList.add('opacity-0');
906
- mainPreview.removeAttribute('src');
907
-
908
  if (!data.result_url) {
909
  previewSkeleton.classList.remove('hidden');
910
  } else {
@@ -912,7 +905,7 @@
912
  }
913
  previewPlaceholder.classList.add('hidden');
914
 
915
- // Create a fresh Image to avoid any caching/onload issues
916
  const tempImg = new Image();
917
  tempImg.onload = () => {
918
  mainPreview.src = tempImg.src;
@@ -952,7 +945,7 @@
952
  mainPreview.src = data.thumb_url;
953
  label.textContent = 'النتيجة';
954
  } else {
955
- mainPreview.src = (data.preview_url || data.result_url) + '?t=' + t;
956
  label.textContent = 'الأصلية';
957
  }
958
  mainPreview.onload = () => {
@@ -1045,6 +1038,7 @@
1045
  if (result.error) throw new Error(result.error);
1046
  imageData[idx].result_url = result.result_url;
1047
  imageData[idx].preview_url = result.preview_url;
 
1048
  imageData[idx].status = 'done';
1049
  imageData[idx].steps = {
1050
  rmbg: document.getElementById('step-rmbg').checked,
 
793
  for (let file of files) {
794
  try {
795
  const data = await uploadFileWithProgress(file);
796
+ imageData.push({ ...data, name: "", id_num: "", result_url: null, preview_url: null, version: 1, custom_crop: null, steps: null, status: 'waiting' });
797
  renderQueue();
798
  if (currentIndex === -1) selectImage(imageData.length - 1);
799
  } catch (e) {
 
840
  return;
841
  }
842
 
 
843
  const html = imageData.map((img, idx) => {
844
  let statusIcon = '';
845
  if (img.status === 'waiting') statusIcon = '<i class="fa-regular fa-clock text-slate-500"></i>';
 
849
 
850
  return `
851
  <div onclick="selectImage(${idx})" class="queue-slide ${currentIndex === idx ? 'active' : ''}">
852
+ <img src="${img.preview_url ? img.preview_url + '?v=' + img.version : img.thumb_url}" alt="">
853
  <div class="slide-status">${statusIcon}</div>
854
  <button onclick="deleteImage(event, ${idx})" class="slide-delete" title="حذف"><i class="fa-solid fa-xmark"></i></button>
855
  <div class="slide-name">${img.filename}</div>
 
894
  reprocessBtn.disabled = !data.result_url;
895
 
896
  const previewSkeleton = document.getElementById('preview-skeleton');
897
+ const url = data.preview_url ? data.preview_url + '?v=' + data.version :
898
+ data.result_url ? data.result_url + '?v=' + data.version :
899
  data.thumb_url;
900
 
 
 
 
 
 
 
901
  if (!data.result_url) {
902
  previewSkeleton.classList.remove('hidden');
903
  } else {
 
905
  }
906
  previewPlaceholder.classList.add('hidden');
907
 
908
+ // Load new image
909
  const tempImg = new Image();
910
  tempImg.onload = () => {
911
  mainPreview.src = tempImg.src;
 
945
  mainPreview.src = data.thumb_url;
946
  label.textContent = 'النتيجة';
947
  } else {
948
+ mainPreview.src = (data.preview_url || data.result_url) + '?v=' + data.version;
949
  label.textContent = 'الأصلية';
950
  }
951
  mainPreview.onload = () => {
 
1038
  if (result.error) throw new Error(result.error);
1039
  imageData[idx].result_url = result.result_url;
1040
  imageData[idx].preview_url = result.preview_url;
1041
+ imageData[idx].version++;
1042
  imageData[idx].status = 'done';
1043
  imageData[idx].steps = {
1044
  rmbg: document.getElementById('step-rmbg').checked,