Opera8 commited on
Commit
2ed17fa
·
verified ·
1 Parent(s): c7b5719

Update templates/editor.html

Browse files
Files changed (1) hide show
  1. templates/editor.html +164 -124
templates/editor.html CHANGED
@@ -65,33 +65,27 @@
65
  .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; }
66
  .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
67
 
68
- /* Refactored Upload Area */
69
- .upload-area { position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 2.5rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); display: flex; flex-direction: column; justify-content: center; align-items: center; overflow: hidden; width: 100%; }
70
- #upload-area-1 { min-height: 200px; }
71
- .upload-area-secondary { min-height: 120px; padding: 1.5rem; }
72
-
73
- .upload-area.drag-over, .upload-area:hover:not(.has-file) { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
74
- .upload-area.has-file { border-style: solid; border-color: var(--panel-border); padding: 0; cursor: default; }
75
-
76
- .upload-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
77
- .upload-area-secondary .upload-content { gap: 0.5rem; }
78
-
79
- .upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; }
80
- .upload-area-secondary .upload-icon svg { width: 28px; height: 28px; }
81
-
82
- .upload-area p { margin: 0; color: var(--text-secondary); font-weight: 500; }
83
- .upload-area-secondary p { font-size: 0.85rem; }
84
-
85
- .preview-image { display: none; width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; }
86
- .remove-file-btn { display: none; position: absolute; top: 12px; right: 12px; background-color: rgba(26, 32, 44, 0.6); color: white; border: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; line-height: 1; z-index: 10; transition: var(--transition-smooth); padding: 0; }
87
- .upload-area-secondary .remove-file-btn { width: 24px; height: 24px; font-size: 0.9rem; top: 8px; right: 8px; }
88
- .remove-file-btn:hover { background-color: var(--danger-color); transform: scale(1.1); }
89
-
90
- .upload-area.has-file .upload-content { display: none; }
91
- .upload-area.has-file .preview-image, .upload-area.has-file .remove-file-btn { display: block; }
92
-
93
- .secondary-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem; }
94
 
 
 
 
 
 
 
 
 
95
  /* Textarea & Buttons */
96
  textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 2px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1.05rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; }
97
  textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 4px var(--accent-primary-glow); background-color: #fff; }
@@ -189,7 +183,6 @@
189
  .image-view-modal { padding: 60px 15px 30px; }
190
  .image-view-toolbar { flex-direction: row; flex-wrap: wrap; padding: 10px; border-radius: 15px; gap: 10px;}
191
  .iv-action-btn { flex: 1; min-width: 130px; font-size: 0.85rem; padding: 10px 12px;}
192
- .secondary-grid { grid-template-columns: 1fr; }
193
  }
194
  </style>
195
  </head>
@@ -208,42 +201,18 @@
208
  <div class="form-group">
209
  <div class="form-label">
210
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>
211
- ۱. تصاویر خود را انتخاب کنید
212
  </div>
213
-
214
- <!-- Main Image 1 -->
215
- <label class="upload-area" id="upload-area-1" for="file-input-1">
216
- <div class="upload-content">
217
- <div class="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
218
- <p>تصویر اصلی را اینجا بکشید یا برای انتخاب کلیک کنید (الزامی)</p>
219
  </div>
220
- <img id="preview-image-1" class="preview-image" src="" alt="Preview">
221
- <button type="button" id="remove-file-btn-1" class="remove-file-btn" title="حذف تصویر">&times;</button>
 
222
  </label>
223
- <input type="file" id="file-input-1" accept="image/png, image/jpeg, image/webp" hidden>
224
-
225
- <!-- Optional Images -->
226
- <div class="secondary-grid">
227
- <label class="upload-area upload-area-secondary" id="upload-area-2" for="file-input-2">
228
- <div class="upload-content">
229
- <div class="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
230
- <p>تصویر دوم (اختیاری)</p>
231
- </div>
232
- <img id="preview-image-2" class="preview-image" src="" alt="Preview">
233
- <button type="button" id="remove-file-btn-2" class="remove-file-btn" title="حذف تصویر">&times;</button>
234
- </label>
235
- <input type="file" id="file-input-2" accept="image/png, image/jpeg, image/webp" hidden>
236
-
237
- <label class="upload-area upload-area-secondary" id="upload-area-3" for="file-input-3">
238
- <div class="upload-content">
239
- <div class="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
240
- <p>تصویر سوم (اختیاری)</p>
241
- </div>
242
- <img id="preview-image-3" class="preview-image" src="" alt="Preview">
243
- <button type="button" id="remove-file-btn-3" class="remove-file-btn" title="حذف تصویر">&times;</button>
244
- </label>
245
- <input type="file" id="file-input-3" accept="image/png, image/jpeg, image/webp" hidden>
246
- </div>
247
  </div>
248
 
249
  <div class="form-group">
@@ -251,7 +220,7 @@
251
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
252
  ۲. دستور ویرایش را بنویسید
253
  </label>
254
- <textarea id="prompt-input" placeholder="مثال: گل در عکس اول را به دست مجسمه در عکس دوم منتقل کن..."></textarea>
255
 
256
  <div id="credit-info" class="credit-info">درحال بررسی اعتبار...</div>
257
  <button id="upgrade-button" class="upgrade-btn">⭐️ ارتقا به نسخه نامحدود</button>
@@ -354,32 +323,44 @@
354
  });
355
  }
356
 
357
- // توابع ذخیره و خواندن تصاویر کاربر
358
- function saveInputImage(blob, index) {
359
  return new Promise((resolve, reject) => {
360
  if (!db) return resolve();
361
  const tx = db.transaction([inputStore], 'readwrite');
362
- tx.objectStore(inputStore).put({ id: 'current_input_' + index, blob: blob });
363
  tx.oncomplete = () => resolve();
364
  tx.onerror = () => reject();
365
  });
366
  }
367
 
368
- function getInputImage(index) {
369
  return new Promise((resolve) => {
370
- if (!db) return resolve(null);
371
  const tx = db.transaction([inputStore], 'readonly');
372
- const req = tx.objectStore(inputStore).get('current_input_' + index);
373
- req.onsuccess = () => resolve(req.result ? req.result.blob : null);
374
- req.onerror = () => resolve(null);
 
 
 
 
 
 
 
 
 
 
 
375
  });
376
  }
377
 
378
- function clearInputImage(index) {
379
  return new Promise((resolve) => {
380
  if (!db) return resolve();
381
  const tx = db.transaction([inputStore], 'readwrite');
382
- tx.objectStore(inputStore).delete('current_input_' + index);
 
383
  tx.oncomplete = () => resolve();
384
  });
385
  }
@@ -498,7 +479,6 @@
498
  });
499
  });
500
 
501
- // نمایش عکس بزرگ همراه با نوار ابزار در زیر آن
502
  window.viewHistoryImage = function(url, promptText) {
503
  const modal = document.createElement('div');
504
  modal.className = 'image-view-modal';
@@ -677,62 +657,90 @@
677
  };
678
 
679
  // --- مدیریت آپلود و متغیرها ---
 
 
 
 
 
680
  const promptInput = document.getElementById('prompt-input');
681
  const submitBtn = document.getElementById('submit-btn');
682
  const imageResultContainer = document.getElementById('image-result-container');
683
 
684
- let files = [null, null, null];
685
  let activePollTimer = null;
686
  let activeTimeoutTimer = null;
687
  let fakeProgressInterval = null;
688
  let isResultProcessed = false;
689
 
690
- async function handleFileSelection(file, index) {
691
- if (!file || !file.type.startsWith('image/')) {
692
- if(file) alert('لطفاً یک تصویر معتبر انتخاب کنید.');
 
 
 
 
 
693
  return;
694
  }
695
- files[index] = file;
696
- await saveInputImage(file, index + 1); // دیتابیس با شماره 1, 2, 3 ذخیره میکند
697
 
698
- const reader = new FileReader();
699
- const preview = document.getElementById(`preview-image-${index+1}`);
700
- const area = document.getElementById(`upload-area-${index+1}`);
701
 
702
- reader.onload = (e) => { preview.src = e.target.result; };
703
- reader.readAsDataURL(file);
704
- area.classList.add('has-file');
705
- checkFormState();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
  }
707
 
708
- async function removeFile(index, e) {
709
- if(e) { e.preventDefault(); e.stopPropagation(); }
710
- files[index] = null;
711
- document.getElementById(`file-input-${index+1}`).value = '';
712
- document.getElementById(`preview-image-${index+1}`).src = '';
713
- document.getElementById(`upload-area-${index+1}`).classList.remove('has-file');
714
- await clearInputImage(index + 1);
 
715
  checkFormState();
716
  }
717
 
718
- // تنظیم Event Listener ها برای هر 3 باکس
719
- for (let i = 0; i < 3; i++) {
720
- const idx = i;
721
- const fileInput = document.getElementById(`file-input-${idx+1}`);
722
- const uploadArea = document.getElementById(`upload-area-${idx+1}`);
723
- const removeBtn = document.getElementById(`remove-file-btn-${idx+1}`);
724
-
725
- fileInput.onchange = (e) => handleFileSelection(e.target.files[0], idx);
726
- uploadArea.ondragover = (e) => { e.preventDefault(); uploadArea.classList.add('drag-over'); };
727
- uploadArea.ondragleave = () => uploadArea.classList.remove('drag-over');
728
- uploadArea.ondrop = (e) => { e.preventDefault(); uploadArea.classList.remove('drag-over'); handleFileSelection(e.dataTransfer.files[0], idx); };
729
- removeBtn.onclick = (e) => removeFile(idx, e);
730
- }
 
731
 
732
  function checkFormState() {
733
  let limitReached = (userSubscriptionStatus === 'free' && freeCreditRemaining === 0);
734
- // فقط عکس اول (files[0]) الزامی است
735
- submitBtn.disabled = limitReached || !(files[0] && promptInput.value.trim() !== '');
736
  }
737
  promptInput.oninput = checkFormState;
738
 
@@ -762,6 +770,27 @@
762
  if(pb) pb.style.width = '100%';
763
  }
764
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  // --- ارسال درخواست به سرور ---
766
  submitBtn.onclick = async () => {
767
  if (userSubscriptionStatus !== 'paid') {
@@ -822,13 +851,29 @@
822
  isResultProcessed = false;
823
 
824
  const formData = new FormData();
825
- // برای سازگاری با بک اند شما عکس اول همان 'image' می‌ماند.
826
- if(files[0]) formData.append('image', files[0]);
827
- if(files[1]) formData.append('image2', files[1]);
828
- if(files[2]) formData.append('image3', files[2]);
829
- formData.append('prompt', originalPrompt);
830
 
831
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
  const res = await fetch('/api/edit', {
833
  method: 'POST',
834
  body: formData
@@ -888,7 +933,7 @@
888
  displayError('خطا در ارتباط با گیت‌هاب سرور.');
889
  }
890
  } catch (e) {
891
- displayError('خطای اتصال به سرور!');
892
  }
893
  };
894
 
@@ -915,18 +960,13 @@
915
  try {
916
  await initDB();
917
 
918
- // بازیابی تصاویر کاربر در صورت رفرش صفحه
919
- for (let i = 0; i < 3; i++) {
920
- const savedInput = await getInputImage(i + 1);
921
- if (savedInput) {
922
- files[i] = savedInput;
923
- const reader = new FileReader();
924
- reader.onload = (e) => { document.getElementById(`preview-image-${i+1}`).src = e.target.result; };
925
- reader.readAsDataURL(savedInput);
926
- document.getElementById(`upload-area-${i+1}`).classList.add('has-file');
927
- }
928
  }
929
- checkFormState();
930
  await loadHistory();
931
  } catch (e) {
932
  console.error(e);
 
65
  .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; }
66
  .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
67
 
68
+ /* Upload Area */
69
+ #upload-area { position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 2.5rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); min-height: 200px; display: flex; flex-direction: column; justify-content: center; align-items: center; overflow: hidden; }
70
+ #upload-area.drag-over, #upload-area:hover:not(.has-file) { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
71
+ #upload-area.has-file { border-style: solid; border-color: var(--panel-border); padding: 0; cursor: default; }
72
+ #upload-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
73
+ #upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; }
74
+ #upload-area p { margin: 0; color: var(--text-secondary); font-weight: 500; }
75
+ #preview-image-main { display: none; width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; }
76
+ #remove-file-btn-main { display: none; position: absolute; top: 12px; right: 12px; background-color: rgba(26, 32, 44, 0.6); color: white; border: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; line-height: 1; z-index: 10; transition: var(--transition-smooth); padding: 0; }
77
+ #remove-file-btn-main:hover { background-color: var(--danger-color); transform: scale(1.1); }
78
+ #upload-area.has-file #upload-content { display: none; }
79
+ #upload-area.has-file #preview-image-main, #upload-area.has-file #remove-file-btn-main { display: block; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ /* Multi-preview Grid CSS */
82
+ #multi-preview-grid { display: none; width: 100%; height: 100%; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; padding: 15px; box-sizing: border-box; align-items: center; justify-content: center; z-index: 5; }
83
+ #upload-area.has-multiple-files #multi-preview-grid { display: grid; }
84
+ #upload-area.has-multiple-files #preview-image-main { display: none !important; }
85
+ .preview-thumb-wrapper { position: relative; width: 100%; height: 140px; border-radius: var(--radius-sm); border: 1px solid var(--input-border); overflow: hidden; background: #000; box-shadow: var(--shadow-sm); }
86
+ .preview-thumb-wrapper img { width: 100%; height: 100%; object-fit: contain; }
87
+ .preview-thumb-badge { position: absolute; bottom: 8px; right: 8px; background: rgba(26, 32, 44, 0.85); color: white; padding: 3px 8px; border-radius: 6px; font-size: 0.75rem; font-weight: bold; font-family: var(--app-font); pointer-events: none; }
88
+
89
  /* Textarea & Buttons */
90
  textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 2px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1.05rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; }
91
  textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 4px var(--accent-primary-glow); background-color: #fff; }
 
183
  .image-view-modal { padding: 60px 15px 30px; }
184
  .image-view-toolbar { flex-direction: row; flex-wrap: wrap; padding: 10px; border-radius: 15px; gap: 10px;}
185
  .iv-action-btn { flex: 1; min-width: 130px; font-size: 0.85rem; padding: 10px 12px;}
 
186
  }
187
  </style>
188
  </head>
 
201
  <div class="form-group">
202
  <div class="form-label">
203
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>
204
+ ۱. تصویر خود را انتخاب کنید (تا ۳ تصویر)
205
  </div>
206
+ <label id="upload-area" for="file-input">
207
+ <div id="upload-content">
208
+ <div id="upload-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
209
+ <p>فایل تصویر را اینجا بکشید یا برای انتخاب کلیک کنید (حداکثر ۳ تصویر)</p>
 
 
210
  </div>
211
+ <img id="preview-image-main" src="" alt="Preview">
212
+ <div id="multi-preview-grid"></div>
213
+ <button type="button" id="remove-file-btn-main" title="حذف تصویر">&times;</button>
214
  </label>
215
+ <input type="file" id="file-input" accept="image/png, image/jpeg, image/webp" multiple hidden>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  </div>
217
 
218
  <div class="form-group">
 
220
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
221
  ۲. دستور ویرایش را بنویسید
222
  </label>
223
+ <textarea id="prompt-input" placeholder="مثال: پس‌زمینه را حذف کن و یک ساحل آفتابی قرار بده"></textarea>
224
 
225
  <div id="credit-info" class="credit-info">درحال بررسی اعتبار...</div>
226
  <button id="upgrade-button" class="upgrade-btn">⭐️ ارتقا به نسخه نامحدود</button>
 
323
  });
324
  }
325
 
326
+ // توابع ذخیره و خواندن تصاویر کاربر (پشتیبانی از چندین تصویر)
327
+ function saveInputImages(blobs) {
328
  return new Promise((resolve, reject) => {
329
  if (!db) return resolve();
330
  const tx = db.transaction([inputStore], 'readwrite');
331
+ tx.objectStore(inputStore).put({ id: 'current_inputs', blobs: blobs });
332
  tx.oncomplete = () => resolve();
333
  tx.onerror = () => reject();
334
  });
335
  }
336
 
337
+ function getInputImages() {
338
  return new Promise((resolve) => {
339
+ if (!db) return resolve([]);
340
  const tx = db.transaction([inputStore], 'readonly');
341
+ const req = tx.objectStore(inputStore).get('current_inputs');
342
+ req.onsuccess = () => {
343
+ if (req.result && req.result.blobs) {
344
+ resolve(req.result.blobs);
345
+ } else {
346
+ // در صورت نبودن، تلاش برای خواندن دیتای تک عکسی قدیمی
347
+ const reqOld = tx.objectStore(inputStore).get('current_input');
348
+ reqOld.onsuccess = () => {
349
+ resolve(reqOld.result ? [reqOld.result.blob] : []);
350
+ };
351
+ reqOld.onerror = () => resolve([]);
352
+ }
353
+ };
354
+ req.onerror = () => resolve([]);
355
  });
356
  }
357
 
358
+ function clearInputImages() {
359
  return new Promise((resolve) => {
360
  if (!db) return resolve();
361
  const tx = db.transaction([inputStore], 'readwrite');
362
+ tx.objectStore(inputStore).delete('current_inputs');
363
+ tx.objectStore(inputStore).delete('current_input');
364
  tx.oncomplete = () => resolve();
365
  });
366
  }
 
479
  });
480
  });
481
 
 
482
  window.viewHistoryImage = function(url, promptText) {
483
  const modal = document.createElement('div');
484
  modal.className = 'image-view-modal';
 
657
  };
658
 
659
  // --- مدیریت آپلود و متغیرها ---
660
+ const uploadArea = document.getElementById('upload-area');
661
+ const fileInput = document.getElementById('file-input');
662
+ const previewImage = document.getElementById('preview-image-main');
663
+ const multiPreviewGrid = document.getElementById('multi-preview-grid');
664
+ const removeFileBtn = document.getElementById('remove-file-btn-main');
665
  const promptInput = document.getElementById('prompt-input');
666
  const submitBtn = document.getElementById('submit-btn');
667
  const imageResultContainer = document.getElementById('image-result-container');
668
 
669
+ let uploadedFiles = [];
670
  let activePollTimer = null;
671
  let activeTimeoutTimer = null;
672
  let fakeProgressInterval = null;
673
  let isResultProcessed = false;
674
 
675
+ // تابع اختصاصی پیش‌نمایش عکس‌ها
676
+ function renderPreviews() {
677
+ multiPreviewGrid.innerHTML = '';
678
+
679
+ if (uploadedFiles.length === 0) {
680
+ uploadArea.classList.remove('has-file');
681
+ uploadArea.classList.remove('has-multiple-files');
682
+ previewImage.src = '';
683
  return;
684
  }
 
 
685
 
686
+ uploadArea.classList.add('has-file');
 
 
687
 
688
+ if (uploadedFiles.length === 1) {
689
+ uploadArea.classList.remove('has-multiple-files');
690
+ const reader = new FileReader();
691
+ reader.onload = (e) => { previewImage.src = e.target.result; };
692
+ reader.readAsDataURL(uploadedFiles[0]);
693
+ } else {
694
+ uploadArea.classList.add('has-multiple-files');
695
+ uploadedFiles.forEach((file, index) => {
696
+ const wrapper = document.createElement('div');
697
+ wrapper.className = 'preview-thumb-wrapper';
698
+
699
+ const img = document.createElement('img');
700
+ const reader = new FileReader();
701
+ reader.onload = (e) => { img.src = e.target.result; };
702
+ reader.readAsDataURL(file);
703
+
704
+ const badge = document.createElement('div');
705
+ badge.className = 'preview-thumb-badge';
706
+ badge.textContent = `تصویر ${index + 1}`;
707
+
708
+ wrapper.appendChild(img);
709
+ wrapper.appendChild(badge);
710
+ multiPreviewGrid.appendChild(wrapper);
711
+ });
712
+ }
713
  }
714
 
715
+ async function handleFiles(files) {
716
+ if (!files || files.length === 0) return;
717
+ const validFiles = Array.from(files).filter(file => file.type.startsWith('image/')).slice(0, 3);
718
+ if (validFiles.length === 0) return alert('لطفاً حداقل یک تصویر معتبر انتخاب کنید.');
719
+
720
+ uploadedFiles = validFiles;
721
+ await saveInputImages(validFiles);
722
+ renderPreviews();
723
  checkFormState();
724
  }
725
 
726
+ removeFileBtn.onclick = async (e) => {
727
+ e.preventDefault(); e.stopPropagation();
728
+ uploadedFiles = []; fileInput.value = ''; previewImage.src = '';
729
+ uploadArea.classList.remove('has-file');
730
+ uploadArea.classList.remove('has-multiple-files');
731
+ multiPreviewGrid.innerHTML = '';
732
+ await clearInputImages();
733
+ checkFormState();
734
+ };
735
+
736
+ fileInput.onchange = (e) => handleFiles(e.target.files);
737
+ uploadArea.ondragover = (e) => { e.preventDefault(); uploadArea.classList.add('drag-over'); };
738
+ uploadArea.ondragleave = () => uploadArea.classList.remove('drag-over');
739
+ uploadArea.ondrop = (e) => { e.preventDefault(); uploadArea.classList.remove('drag-over'); handleFiles(e.dataTransfer.files); };
740
 
741
  function checkFormState() {
742
  let limitReached = (userSubscriptionStatus === 'free' && freeCreditRemaining === 0);
743
+ submitBtn.disabled = limitReached || !(uploadedFiles.length > 0 && promptInput.value.trim() !== '');
 
744
  }
745
  promptInput.oninput = checkFormState;
746
 
 
770
  if(pb) pb.style.width = '100%';
771
  }
772
 
773
+ // متد مستقیم آپلود تصویر دوم و سوم به کادر موقت گرادیو
774
+ async function uploadGradioFile(file) {
775
+ if (!file) return "";
776
+ const API_BASE = "https://selfit-camera-omni-image-editor.hf.space";
777
+ const formData = new FormData();
778
+ formData.append("files", file);
779
+
780
+ try {
781
+ const res = await fetch(`${API_BASE}/gradio_api/upload`, {
782
+ method: "POST",
783
+ body: formData
784
+ });
785
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
786
+ const paths = await res.json();
787
+ return paths[0];
788
+ } catch (error) {
789
+ console.error("Helper upload failed:", error);
790
+ throw error;
791
+ }
792
+ }
793
+
794
  // --- ارسال درخواست به سرور ---
795
  submitBtn.onclick = async () => {
796
  if (userSubscriptionStatus !== 'paid') {
 
851
  isResultProcessed = false;
852
 
853
  const formData = new FormData();
 
 
 
 
 
854
 
855
  try {
856
+ let finalPrompt = originalPrompt;
857
+
858
+ // در صورتی که بیش از ۱ تصویر وجود داشته باشد
859
+ if (uploadedFiles.length > 1) {
860
+ const loadingText = document.getElementById('loading-text');
861
+ if (loadingText) loadingText.textContent = "در حال آپلود و هماهنگ‌سازی تصاویر کمکی...";
862
+
863
+ // آپلود فایل‌ها به سرور برای دریافت مسیرهای Gradio
864
+ const p1 = uploadGradioFile(uploadedFiles[0]);
865
+ const p2 = uploadGradioFile(uploadedFiles[1]);
866
+ const p3 = uploadedFiles[2] ? uploadGradioFile(uploadedFiles[2]) : Promise.resolve("");
867
+
868
+ const [path1, path2, path3] = await Promise.all([p1, p2, p3]);
869
+
870
+ // قرار دادن ساختار مسیرها در کادر پیام مخفی شده برای رانر بک‌اند پایتون
871
+ finalPrompt = `${originalPrompt}\n---OMNI_MULTI---${path1},${path2},${path3}`;
872
+ }
873
+
874
+ formData.append('image', uploadedFiles[0]);
875
+ formData.append('prompt', finalPrompt);
876
+
877
  const res = await fetch('/api/edit', {
878
  method: 'POST',
879
  body: formData
 
933
  displayError('خطا در ارتباط با گیت‌هاب سرور.');
934
  }
935
  } catch (e) {
936
+ displayError('خطای اتصال به سرور یا آپلود تصاویر به سرور کمکی!');
937
  }
938
  };
939
 
 
960
  try {
961
  await initDB();
962
 
963
+ const savedInputs = await getInputImages();
964
+ if (savedInputs && savedInputs.length > 0) {
965
+ uploadedFiles = savedInputs;
966
+ renderPreviews();
967
+ checkFormState();
 
 
 
 
 
968
  }
969
+
970
  await loadHistory();
971
  } catch (e) {
972
  console.error(e);