Elias207 commited on
Commit
97e59b7
·
verified ·
1 Parent(s): bc75202

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +93 -25
index.html CHANGED
@@ -34,6 +34,18 @@
34
 
35
  @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
36
  @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  body {
39
  font-family: var(--app-font);
@@ -127,7 +139,6 @@
127
  textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px 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: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 90px; resize: vertical; }
128
  textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
129
 
130
- /* --- MODIFICATION START: Improved Button Style --- */
131
  #submit-btn {
132
  display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem;
133
  font-size: 1.2rem; font-weight: 700;
@@ -150,10 +161,17 @@
150
  #submit-btn:hover:not(:disabled) svg {
151
  transform: scale(1.1) rotate(-15deg);
152
  }
153
- #submit-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; }
154
- #submit-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
 
 
 
 
 
155
  /* --- MODIFICATION END --- */
156
 
 
 
157
  #result-container { min-height: 350px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; align-items: center; justify-content: center; }
158
  #result-container.loading, #result-container.has-content { border-style: solid; border-color: var(--panel-border); }
159
  #loading-placeholder { display: none; }
@@ -344,6 +362,30 @@
344
  .modal-btn.cancel-btn { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
345
  .modal-btn.cancel-btn:hover { background-color: var(--panel-border); color: var(--text-primary); }
346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  @media (max-width: 768px) {
348
  main, .gallery-section { padding: 1.5rem; } h1 { font-size: 2.2rem; } .gallery-section { padding: 1rem; }
349
  #history-grid { gap: 0.75rem; } .history-item { padding: 0.75rem; }
@@ -452,6 +494,10 @@
452
  </div>
453
  </div>
454
 
 
 
 
 
455
  <script>
456
  // --- START OF MODIFIED APPLICATION LOGIC SCRIPT ---
457
  const API_BASE_URL = "https://ginigen-nano-banana-video.hf.space/gradio_api/";
@@ -512,45 +558,47 @@
512
  };
513
  const showConfirmationModal = (message, onConfirm) => { modalMessageText.textContent = message; confirmationModal.classList.add('visible'); modalConfirmBtn.onclick = () => { onConfirm(); hideConfirmationModal(); }; modalCancelBtn.onclick = () => { hideConfirmationModal(); }; }; const hideConfirmationModal = () => { confirmationModal.classList.remove('visible'); modalConfirmBtn.onclick = null; modalCancelBtn.onclick = null; }; const resetUploader = () => { uploadedFile = null; fileInput.value = ''; previewImage.src = ''; uploadArea.classList.remove('has-file'); checkFormState(); }; const checkFormState = () => { submitBtn.disabled = !uploadedFile || promptInput.value.trim() === ''; }; const setLoading = (isLoading) => { const btnSpinner = submitBtn.querySelector('.spinner'); const btnText = document.getElementById('btn-text'); const btnIcon = submitBtn.querySelector('svg'); if (isLoading) { resultContainer.classList.remove('has-content'); resultContainer.classList.add('loading'); btnSpinner.style.display = 'inline-block'; btnIcon.style.display = 'none'; btnText.textContent = 'در حال پردازش...'; submitBtn.disabled = true; resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; } else { resultContainer.classList.remove('loading'); btnSpinner.style.display = 'none'; btnIcon.style.display = 'inline-block'; btnText.textContent = 'ایجاد کن'; checkFormState(); } }; const displayResult = (imageUrls) => { resultGrid.innerHTML = ''; imageUrls.forEach((url) => { const img = document.createElement('img'); img.src = url; img.alt = 'تصویر ویرایش شده'; img.addEventListener('click', () => openLightbox(url, imageUrls)); resultGrid.appendChild(img); }); resultContainer.classList.add('has-content'); }; const clearResult = () => { resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; resultContainer.classList.remove('has-content'); }; const displayError = (message) => { errorMessage.textContent = message; errorMessage.style.display = 'block'; }; const updateLightboxImage = () => { const newUrl = currentLightboxGroup[currentLightboxIndex]; lightboxImg.src = newUrl; currentLightboxUrl = newUrl; }; const openLightbox = (clickedUrl, urlGroup) => { currentLightboxGroup = urlGroup; currentLightboxIndex = urlGroup.indexOf(clickedUrl); updateLightboxImage(); lightboxNext.style.display = urlGroup.length > 1 ? 'flex' : 'none'; lightbox.classList.add('visible'); }; const closeLightbox = () => { lightbox.classList.remove('visible'); currentLightboxGroup = []; }; const handleDownload = async () => { if (!currentLightboxUrl) return; const spinner = lightboxDownload.querySelector('.spinner'); const icon = lightboxDownload.querySelector('svg'); spinner.style.display = 'block'; icon.style.display = 'none'; lightboxDownload.disabled = true; try { const response = await fetch(currentLightboxUrl); if (!response.ok) throw new Error('Network response was not ok.'); const blob = await response.blob(); const objectUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = objectUrl; a.download = `ai-edited-image-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(objectUrl); } catch (e) { console.error('Download failed:', e); alert('خطا در دانلود تصویر. لطفا دوباره تلاش کنید.'); } finally { spinner.style.display = 'none'; icon.style.display = 'block'; lightboxDownload.disabled = false; } };
514
 
515
- // --- MODIFICATION START: History Management with Expiration ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  const getHistory = () => {
517
  const historyFromStorage = JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
518
  if (!Array.isArray(historyFromStorage)) return [];
519
 
520
  const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000;
521
  const now = Date.now();
522
-
523
- // Filter out items older than 5 days
524
  const freshHistory = historyFromStorage.filter(item => {
525
- // For backward compatibility, if 'createdAt' timestamp doesn't exist, we keep the item.
526
- // This prevents deleting old history items that were created before this feature was added.
527
- if (typeof item.createdAt !== 'number') {
528
- return true;
529
- }
530
  return (now - item.createdAt) < FIVE_DAYS_IN_MS;
531
  });
532
-
533
- // If the filtering process removed any items, we update localStorage to clean it up.
534
  if (freshHistory.length < historyFromStorage.length) {
535
  localStorage.setItem('aiPhotoshopHistory', JSON.stringify(freshHistory));
536
  }
537
-
538
  return freshHistory;
539
  };
540
 
541
  const saveHistory = (history) => { localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history)); renderHistory(); };
542
 
543
  const addToHistory = (item) => {
544
- let history = getHistory(); // getHistory now filters expired items
545
- const newItem = {
546
- ...item,
547
- createdAt: Date.now() // Add timestamp when creating a history item
548
- };
549
  history.unshift(newItem);
550
  if (history.length > 15) history.pop();
551
  saveHistory(history);
552
  };
553
- // --- MODIFICATION END ---
554
 
555
  const handleClearHistory = () => { showConfirmationModal('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟ این عمل غیرقابل بازگشت است.', () => { saveHistory([]); }); }; const handleDeleteItem = (index) => { showConfirmationModal('آیا از حذف این مورد از تاریخچه مطمئن هستید؟', () => { let history = getHistory(); history.splice(index, 1); saveHistory(history); }); }; const renderHistory = () => { const history = getHistory(); historyGrid.innerHTML = ''; clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none'; history.forEach((item, index) => { const card = document.createElement('div'); card.className = 'history-item'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'history-delete-btn'; deleteBtn.title = 'حذف این مورد'; deleteBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`; deleteBtn.onclick = () => handleDeleteItem(index); card.appendChild(deleteBtn); const imageGrid = document.createElement('div'); imageGrid.className = 'history-item-grid'; item.urls.forEach(url => { const img = document.createElement('img'); img.src = url; img.alt = 'تصویر از تاریخچه'; img.addEventListener('click', () => openLightbox(url, item.urls)); imageGrid.appendChild(img); }); card.appendChild(imageGrid); historyGrid.appendChild(card); }); };
556
 
@@ -614,7 +662,7 @@
614
  }
615
 
616
  if (resultUrl) {
617
- displayResult([resultUrl]); // displayResult expects an array
618
  addToHistory({ prompt: promptInput.value.trim(), urls: [resultUrl] });
619
  } else {
620
  throw new Error('نتیجه دریافت شد اما لینک تصویر معتبر نبود.');
@@ -641,11 +689,30 @@
641
  document.addEventListener('DOMContentLoaded', () => { renderHistory(); checkFormState(); });
642
  removeFileBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetUploader(); }); fileInput.addEventListener('change', (e) => handleFile(e.target.files[0])); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }); }); ['dragenter', 'dragover'].forEach(e => { uploadArea.addEventListener(e, () => { if (!uploadArea.classList.contains('has-file')) uploadArea.classList.add('drag-over'); }); }); ['dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, () => uploadArea.classList.remove('drag-over')); }); uploadArea.addEventListener('drop', e => { if (!uploadArea.classList.contains('has-file')) handleFile(e.dataTransfer.files[0]); }); promptInput.addEventListener('input', checkFormState);
643
 
644
- // --- SUBMIT BUTTON LOGIC ---
645
  submitBtn.addEventListener('click', async () => {
646
- if (submitBtn.disabled) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  setLoading(true);
648
-
649
  try {
650
  const sessionHash = Math.random().toString(36).substring(7);
651
  const textOverlay = document.querySelector('.text-overlay');
@@ -663,9 +730,10 @@
663
  } catch (error) {
664
  console.error('An error occurred:', error);
665
  displayError(error.message);
666
- setLoading(false); // Make sure to stop loading on initial error
667
  }
668
  });
 
669
 
670
  lightboxClose.addEventListener('click', closeLightbox); lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); }); lightboxDownload.addEventListener('click', handleDownload); lightboxNext.addEventListener('click', () => { currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxGroup.length; updateLightboxImage(); }); clearHistoryBtn.addEventListener('click', handleClearHistory);
671
  </script>
 
34
 
35
  @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
36
  @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
37
+
38
+ /* --- MODIFICATION START: Shake Animation --- */
39
+ @keyframes shake-error {
40
+ 10%, 90% { transform: translateX(-2px); }
41
+ 20%, 80% { transform: translateX(4px); }
42
+ 30%, 50%, 70% { transform: translateX(-6px); }
43
+ 40%, 60% { transform: translateX(6px); }
44
+ }
45
+ .shake-error {
46
+ animation: shake-error 0.7s cubic-bezier(.36,.07,.19,.97) both;
47
+ }
48
+ /* --- MODIFICATION END --- */
49
 
50
  body {
51
  font-family: var(--app-font);
 
139
  textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px 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: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 90px; resize: vertical; }
140
  textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
141
 
 
142
  #submit-btn {
143
  display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem;
144
  font-size: 1.2rem; font-weight: 700;
 
161
  #submit-btn:hover:not(:disabled) svg {
162
  transform: scale(1.1) rotate(-15deg);
163
  }
164
+
165
+ /* --- MODIFICATION START: Disabled Button Style --- */
166
+ #submit-btn:disabled {
167
+ cursor: not-allowed;
168
+ box-shadow: none;
169
+ opacity: 0.65;
170
+ }
171
  /* --- MODIFICATION END --- */
172
 
173
+ #submit-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none; }
174
+
175
  #result-container { min-height: 350px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; align-items: center; justify-content: center; }
176
  #result-container.loading, #result-container.has-content { border-style: solid; border-color: var(--panel-border); }
177
  #loading-placeholder { display: none; }
 
362
  .modal-btn.cancel-btn { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
363
  .modal-btn.cancel-btn:hover { background-color: var(--panel-border); color: var(--text-primary); }
364
 
365
+ /* --- MODIFICATION START: Toast Notification Style --- */
366
+ #toast-notification {
367
+ position: fixed;
368
+ bottom: 20px;
369
+ left: 50%;
370
+ transform: translate(-50%, 20px);
371
+ background-color: var(--danger-color);
372
+ color: white;
373
+ padding: 1rem 1.5rem;
374
+ border-radius: var(--radius-btn);
375
+ box-shadow: var(--shadow-lg);
376
+ font-weight: 600;
377
+ z-index: 3000;
378
+ opacity: 0;
379
+ transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
380
+ pointer-events: none;
381
+ }
382
+ #toast-notification.visible {
383
+ opacity: 1;
384
+ transform: translate(-50%, 0);
385
+ pointer-events: auto;
386
+ }
387
+ /* --- MODIFICATION END --- */
388
+
389
  @media (max-width: 768px) {
390
  main, .gallery-section { padding: 1.5rem; } h1 { font-size: 2.2rem; } .gallery-section { padding: 1rem; }
391
  #history-grid { gap: 0.75rem; } .history-item { padding: 0.75rem; }
 
494
  </div>
495
  </div>
496
 
497
+ <!-- MODIFICATION START: Toast Notification HTML -->
498
+ <div id="toast-notification"></div>
499
+ <!-- MODIFICATION END -->
500
+
501
  <script>
502
  // --- START OF MODIFIED APPLICATION LOGIC SCRIPT ---
503
  const API_BASE_URL = "https://ginigen-nano-banana-video.hf.space/gradio_api/";
 
558
  };
559
  const showConfirmationModal = (message, onConfirm) => { modalMessageText.textContent = message; confirmationModal.classList.add('visible'); modalConfirmBtn.onclick = () => { onConfirm(); hideConfirmationModal(); }; modalCancelBtn.onclick = () => { hideConfirmationModal(); }; }; const hideConfirmationModal = () => { confirmationModal.classList.remove('visible'); modalConfirmBtn.onclick = null; modalCancelBtn.onclick = null; }; const resetUploader = () => { uploadedFile = null; fileInput.value = ''; previewImage.src = ''; uploadArea.classList.remove('has-file'); checkFormState(); }; const checkFormState = () => { submitBtn.disabled = !uploadedFile || promptInput.value.trim() === ''; }; const setLoading = (isLoading) => { const btnSpinner = submitBtn.querySelector('.spinner'); const btnText = document.getElementById('btn-text'); const btnIcon = submitBtn.querySelector('svg'); if (isLoading) { resultContainer.classList.remove('has-content'); resultContainer.classList.add('loading'); btnSpinner.style.display = 'inline-block'; btnIcon.style.display = 'none'; btnText.textContent = 'در حال پردازش...'; submitBtn.disabled = true; resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; } else { resultContainer.classList.remove('loading'); btnSpinner.style.display = 'none'; btnIcon.style.display = 'inline-block'; btnText.textContent = 'ایجاد کن'; checkFormState(); } }; const displayResult = (imageUrls) => { resultGrid.innerHTML = ''; imageUrls.forEach((url) => { const img = document.createElement('img'); img.src = url; img.alt = 'تصویر ویرایش شده'; img.addEventListener('click', () => openLightbox(url, imageUrls)); resultGrid.appendChild(img); }); resultContainer.classList.add('has-content'); }; const clearResult = () => { resultGrid.innerHTML = ''; errorMessage.style.display = 'none'; resultContainer.classList.remove('has-content'); }; const displayError = (message) => { errorMessage.textContent = message; errorMessage.style.display = 'block'; }; const updateLightboxImage = () => { const newUrl = currentLightboxGroup[currentLightboxIndex]; lightboxImg.src = newUrl; currentLightboxUrl = newUrl; }; const openLightbox = (clickedUrl, urlGroup) => { currentLightboxGroup = urlGroup; currentLightboxIndex = urlGroup.indexOf(clickedUrl); updateLightboxImage(); lightboxNext.style.display = urlGroup.length > 1 ? 'flex' : 'none'; lightbox.classList.add('visible'); }; const closeLightbox = () => { lightbox.classList.remove('visible'); currentLightboxGroup = []; }; const handleDownload = async () => { if (!currentLightboxUrl) return; const spinner = lightboxDownload.querySelector('.spinner'); const icon = lightboxDownload.querySelector('svg'); spinner.style.display = 'block'; icon.style.display = 'none'; lightboxDownload.disabled = true; try { const response = await fetch(currentLightboxUrl); if (!response.ok) throw new Error('Network response was not ok.'); const blob = await response.blob(); const objectUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = objectUrl; a.download = `ai-edited-image-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(objectUrl); } catch (e) { console.error('Download failed:', e); alert('خطا در دانلود تصویر. لطفا دوباره تلاش کنید.'); } finally { spinner.style.display = 'none'; icon.style.display = 'block'; lightboxDownload.disabled = false; } };
560
 
561
+ // --- MODIFICATION START: Toast Notification Logic ---
562
+ let toastTimeout;
563
+ const showToast = (message) => {
564
+ const toast = document.getElementById('toast-notification');
565
+ if (!toast) return;
566
+
567
+ clearTimeout(toastTimeout);
568
+ toast.textContent = message;
569
+ toast.classList.add('visible');
570
+
571
+ toastTimeout = setTimeout(() => {
572
+ toast.classList.remove('visible');
573
+ }, 3500);
574
+ };
575
+ // --- MODIFICATION END ---
576
+
577
  const getHistory = () => {
578
  const historyFromStorage = JSON.parse(localStorage.getItem('aiPhotoshopHistory') || '[]');
579
  if (!Array.isArray(historyFromStorage)) return [];
580
 
581
  const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000;
582
  const now = Date.now();
 
 
583
  const freshHistory = historyFromStorage.filter(item => {
584
+ if (typeof item.createdAt !== 'number') { return true; }
 
 
 
 
585
  return (now - item.createdAt) < FIVE_DAYS_IN_MS;
586
  });
 
 
587
  if (freshHistory.length < historyFromStorage.length) {
588
  localStorage.setItem('aiPhotoshopHistory', JSON.stringify(freshHistory));
589
  }
 
590
  return freshHistory;
591
  };
592
 
593
  const saveHistory = (history) => { localStorage.setItem('aiPhotoshopHistory', JSON.stringify(history)); renderHistory(); };
594
 
595
  const addToHistory = (item) => {
596
+ let history = getHistory();
597
+ const newItem = { ...item, createdAt: Date.now() };
 
 
 
598
  history.unshift(newItem);
599
  if (history.length > 15) history.pop();
600
  saveHistory(history);
601
  };
 
602
 
603
  const handleClearHistory = () => { showConfirmationModal('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟ این عمل غیرقابل بازگشت است.', () => { saveHistory([]); }); }; const handleDeleteItem = (index) => { showConfirmationModal('آیا از حذف این مورد از تاریخچه مطمئن هستید؟', () => { let history = getHistory(); history.splice(index, 1); saveHistory(history); }); }; const renderHistory = () => { const history = getHistory(); historyGrid.innerHTML = ''; clearHistoryBtn.style.display = history.length > 0 ? 'flex' : 'none'; history.forEach((item, index) => { const card = document.createElement('div'); card.className = 'history-item'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'history-delete-btn'; deleteBtn.title = 'حذف این مورد'; deleteBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`; deleteBtn.onclick = () => handleDeleteItem(index); card.appendChild(deleteBtn); const imageGrid = document.createElement('div'); imageGrid.className = 'history-item-grid'; item.urls.forEach(url => { const img = document.createElement('img'); img.src = url; img.alt = 'تصویر از تاریخچه'; img.addEventListener('click', () => openLightbox(url, item.urls)); imageGrid.appendChild(img); }); card.appendChild(imageGrid); historyGrid.appendChild(card); }); };
604
 
 
662
  }
663
 
664
  if (resultUrl) {
665
+ displayResult([resultUrl]);
666
  addToHistory({ prompt: promptInput.value.trim(), urls: [resultUrl] });
667
  } else {
668
  throw new Error('نتیجه دریافت شد اما لینک تصویر معتبر نبود.');
 
689
  document.addEventListener('DOMContentLoaded', () => { renderHistory(); checkFormState(); });
690
  removeFileBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); resetUploader(); }); fileInput.addEventListener('change', (e) => handleFile(e.target.files[0])); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, p => { p.preventDefault(); p.stopPropagation(); }); }); ['dragenter', 'dragover'].forEach(e => { uploadArea.addEventListener(e, () => { if (!uploadArea.classList.contains('has-file')) uploadArea.classList.add('drag-over'); }); }); ['dragleave', 'drop'].forEach(e => { uploadArea.addEventListener(e, () => uploadArea.classList.remove('drag-over')); }); uploadArea.addEventListener('drop', e => { if (!uploadArea.classList.contains('has-file')) handleFile(e.dataTransfer.files[0]); }); promptInput.addEventListener('input', checkFormState);
691
 
692
+ // --- MODIFICATION START: New Submit Button Logic ---
693
  submitBtn.addEventListener('click', async () => {
694
+ const isFormInvalid = !uploadedFile || promptInput.value.trim() === '';
695
+
696
+ if (isFormInvalid) {
697
+ submitBtn.classList.add('shake-error');
698
+ submitBtn.addEventListener('animationend', () => {
699
+ submitBtn.classList.remove('shake-error');
700
+ }, { once: true });
701
+
702
+ let message = '';
703
+ if (!uploadedFile && promptInput.value.trim() === '') {
704
+ message = 'لطفا ابتدا تصویر و دستور ویرایش را وارد کنید.';
705
+ } else if (!uploadedFile) {
706
+ message = 'لطفا ابتدا یک تصویر انتخاب کنید.';
707
+ } else {
708
+ message = 'لطفا دستور ویرایش را بنویسید.';
709
+ }
710
+ showToast(message);
711
+ return;
712
+ }
713
+
714
+ // This part only runs if the form is valid
715
  setLoading(true);
 
716
  try {
717
  const sessionHash = Math.random().toString(36).substring(7);
718
  const textOverlay = document.querySelector('.text-overlay');
 
730
  } catch (error) {
731
  console.error('An error occurred:', error);
732
  displayError(error.message);
733
+ setLoading(false);
734
  }
735
  });
736
+ // --- MODIFICATION END ---
737
 
738
  lightboxClose.addEventListener('click', closeLightbox); lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); }); lightboxDownload.addEventListener('click', handleDownload); lightboxNext.addEventListener('click', () => { currentLightboxIndex = (currentLightboxIndex + 1) % currentLightboxGroup.length; updateLightboxImage(); }); clearHistoryBtn.addEventListener('click', handleClearHistory);
739
  </script>