Update templates/editor.html
Browse files- 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 |
-
/*
|
| 69 |
-
|
| 70 |
-
#upload-area-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 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 |
-
|
| 215 |
-
|
| 216 |
-
|
| 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-
|
| 221 |
-
<
|
|
|
|
| 222 |
</label>
|
| 223 |
-
<input type="file" id="file-input
|
| 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="حذف تصویر">×</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="حذف تصویر">×</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="مثال:
|
| 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
|
| 359 |
return new Promise((resolve, reject) => {
|
| 360 |
if (!db) return resolve();
|
| 361 |
const tx = db.transaction([inputStore], 'readwrite');
|
| 362 |
-
tx.objectStore(inputStore).put({ id: '
|
| 363 |
tx.oncomplete = () => resolve();
|
| 364 |
tx.onerror = () => reject();
|
| 365 |
});
|
| 366 |
}
|
| 367 |
|
| 368 |
-
function
|
| 369 |
return new Promise((resolve) => {
|
| 370 |
-
if (!db) return resolve(
|
| 371 |
const tx = db.transaction([inputStore], 'readonly');
|
| 372 |
-
const req = tx.objectStore(inputStore).get('
|
| 373 |
-
req.onsuccess = () =>
|
| 374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
});
|
| 376 |
}
|
| 377 |
|
| 378 |
-
function
|
| 379 |
return new Promise((resolve) => {
|
| 380 |
if (!db) return resolve();
|
| 381 |
const tx = db.transaction([inputStore], 'readwrite');
|
| 382 |
-
tx.objectStore(inputStore).delete('
|
|
|
|
| 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
|
| 685 |
let activePollTimer = null;
|
| 686 |
let activeTimeoutTimer = null;
|
| 687 |
let fakeProgressInterval = null;
|
| 688 |
let isResultProcessed = false;
|
| 689 |
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
return;
|
| 694 |
}
|
| 695 |
-
files[index] = file;
|
| 696 |
-
await saveInputImage(file, index + 1); // دیتابیس با شماره 1, 2, 3 ذخیره میکند
|
| 697 |
|
| 698 |
-
|
| 699 |
-
const preview = document.getElementById(`preview-image-${index+1}`);
|
| 700 |
-
const area = document.getElementById(`upload-area-${index+1}`);
|
| 701 |
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
}
|
| 707 |
|
| 708 |
-
async function
|
| 709 |
-
if(
|
| 710 |
-
files
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
await
|
|
|
|
| 715 |
checkFormState();
|
| 716 |
}
|
| 717 |
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
|
|
|
| 731 |
|
| 732 |
function checkFormState() {
|
| 733 |
let limitReached = (userSubscriptionStatus === 'free' && freeCreditRemaining === 0);
|
| 734 |
-
|
| 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 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 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 |
-
|
| 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="حذف تصویر">×</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);
|