1var SELF = '/data.php?image'; 2var COSTS = window.IMAGE_GENERATION_COSTS; 3var FORM_KEY = 'ai_image_form'; 4var PROMPT_KEY = 'ai_image_prompt:' + window.location.pathname; 5var IMG_KEY = 'ai_image_result'; 6var TASK_KEY = 'ai_image_task'; 7var POLL_MS = 6000; 8var TASK_TTL = 30 * 60 * 1000; // Covers the full OpenAI -> Nano Banana -> WAN -> Seedream chain. 9var pollTimer = null; 10var imagePollVersion = 0; 11var imageProgressTimer = null; 12var imageProgressStartedAt = 0; 13var imageProgressServerState = 'starting'; 14var isPaidUser = false; 15var _btnEnabledAt = 0; 16var imageCooldownTimer = null; 17function imageCooldownUntil() { 18 var cookie = document.cookie.match(/(?:^|;\s*)image_generation_until=(\d+)/); 19 return Math.max(_btnEnabledAt, cookie ? Number(cookie[1]) * 1000 : 0); 20} 21var UPLOADS_KEY = 'ai_image_uploads'; 22 23/* СпиÑок загÑÑженнÑÑ Ð¸Ð·Ð¾Ð±Ñажений: [{ id, previewSrc, kieUrl, status: 'uploading'|'ok'|'error', errMsg }] */ 24var uploadedImages = []; 25 26function scheduleUploadExpiry(id, savedAt) { 27 var TTL = 2 * 60 * 60 * 1000; 28 var remaining = TTL - (Date.now() - savedAt); 29 if (remaining <= 0) { return; } 30 setTimeout(function() { removeImage(id); }, remaining); 31} 32 33function saveUploads() { 34 try { 35 var toSave = uploadedImages 36 .filter(function(img) { return img.status === 'ok' && img.kieUrl; }) 37 .map(function(img) { 38 return { id: img.id, kieUrl: img.kieUrl, savedAt: img.savedAt || Date.now() }; 39 }); 40 localStorage.setItem(UPLOADS_KEY, JSON.stringify(toSave)); 41 } catch(e) {} 42} 43 44function restoreUploads() { 45 try { 46 var saved = JSON.parse(localStorage.getItem(UPLOADS_KEY)); 47 if (!saved || !saved.length) return; 48 var TTL_14 = 2 * 60 * 60 * 1000; 49 var now = Date.now(); 50 saved.forEach(function(item) { 51 if (item.savedAt && (now - item.savedAt) > TTL_14) return; // ÑÑÑаÑело â пÑопÑÑкаем 52 var restoredSavedAt = item.savedAt || now; 53 uploadedImages.push({ 54 id: item.id, 55 previewSrc: sugUrl(item.kieUrl), 56 kieUrl: item.kieUrl, 57 savedAt: restoredSavedAt, 58 status: 'ok' 59 }); 60 scheduleUploadExpiry(item.id, restoredSavedAt); 61 }); 62 renderPreviews(); 63 } catch(e) {} 64} 65 66function getMaxImages() { 67 var ns = document.getElementById('nsmode'); 68 if (ns && ns.checked) return 1; 69 var fm = document.getElementById('fastModel'); 70 var max = (fm && fm.checked) ? 9 : 14; 71 var landing = window.imageLandingConfig; 72 if (landing && landing.maxImages) max = Math.min(max, parseInt(landing.maxImages, 10) || max); 73 return max; 74} 75 76function updateUploadMaxHint() { 77 var hint = document.getElementById('uploadMaxHint'); 78 if (hint) hint.textContent = 'ÐажмиÑе, пеÑеÑаÑиÑе или вÑÑавÑÑе изобÑÐ°Ð¶ÐµÐ½Ð¸Ñ (Ctrl+V, Ð¾Ñ 1 до ' + getMaxImages() + ')'; 79} 80 81function updateNsUploadWarn() { 82 var ns = document.getElementById('nsmode'); 83 var nsWarn = document.getElementById('nsUploadWarn'); 84 if (ns && ns.checked && !nsWarn) { 85 nsWarn = document.createElement('div'); 86 nsWarn.id = 'nsUploadWarn'; 87 nsWarn.style.cssText = 'margin-top:8px;padding:8px 12px;border-radius:8px;' + 88 'background:#fff8e1;border:1px solid #ffe082;font-size:13px;color:#7a5f00;line-height:1.5;'; 89 nsWarn.innerHTML = '⚠️ ÐÐ»Ñ Ð¼Ð¾Ð´ÐµÐ»Ð¸ Ñ Ð¼Ð¸Ð½Ð¸Ð¼Ð°Ð»Ñной модеÑаÑией допÑÑкаеÑÑÑ Ð½Ðµ более 1 иÑÑ Ð¾Ð´Ð½Ð¾Ð³Ð¾ изобÑажениÑ. ÐÑли нÑжно болÑÑе, Ñо ÑбеÑиÑе ÑÑÑ Ð³Ð°Ð»Ð¾ÑкÑ.'; 90 document.getElementById('uploadZone').insertAdjacentElement('afterend', nsWarn); 91 } 92 if (nsWarn) nsWarn.style.display = ns && ns.checked ? 'block' : 'none'; 93} 94 95var MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 ÐÐ 96var PREPARED_FILE_SIZE = 19 * 1024 * 1024; // Ð·Ð°Ð¿Ð°Ñ Ð´Ð»Ñ ÑеÑвеÑной загÑÑзки и WAN 97var _imgBlocked = false; 98var _ratioRequired = false; 99 100function loadImageForPrepare(file) { 101 return new Promise(function(resolve, reject) { 102 var src = URL.createObjectURL(file); 103 var image = new Image(); 104 image.onload = function() { 105 URL.revokeObjectURL(src); 106 resolve(image); 107 }; 108 image.onerror = function() { 109 URL.revokeObjectURL(src); 110 reject(new Error('ÐÑаÑÐ·ÐµÑ Ð½Ðµ Ñмог пÑоÑиÑаÑÑ Ð¸Ð·Ð¾Ð±Ñажение')); 111 }; 112 image.src = src; 113 }); 114} 115 116function canvasToImageBlob(canvas, mime, quality) { 117 return new Promise(function(resolve) { 118 canvas.toBlob(function(blob) { resolve(blob); }, mime, quality); 119 }); 120} 121 122async function prepareLargeImage(file) { 123 var image = await loadImageForPrepare(file);
124 var width = image.naturalWidth || image.width; 125 var height = image.naturalHeight || image.height; 126 if (!width || !height || width * height > 50000000) { 127 throw new Error('СлиÑком болÑÑое ÑазÑеÑение изобÑажениÑ'); 128 } 129 130 var baseScale = Math.min(1, 4096 / Math.max(width, height)); 131 var attempts = [ 132 [baseScale, 0.88], 133 [baseScale * 0.85, 0.80], 134 [baseScale * 0.70, 0.72], 135 [baseScale * 0.55, 0.66] 136 ]; 137 var originalBase = (file.name || 'image').replace(/\.[^.]+$/, ''); 138 139 for (var i = 0; i < attempts.length; i++) { 140 var scale = Math.max(0.1, attempts[i][0]); 141 var canvas = document.createElement('canvas'); 142 canvas.width = Math.max(1, Math.round(width * scale)); 143 canvas.height = Math.max(1, Math.round(height * scale)); 144 var ctx = canvas.getContext('2d'); 145 if (!ctx) throw new Error('ÐÑаÑÐ·ÐµÑ Ð½Ðµ поддеÑÐ¶Ð¸Ð²Ð°ÐµÑ ÑменÑÑение изобÑажений'); 146 ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 147 148 var blob = await canvasToImageBlob(canvas, 'image/webp', attempts[i][1]); 149 if (!blob) blob = await canvasToImageBlob(canvas, 'image/jpeg', attempts[i][1]); 150 canvas.width = canvas.height = 1; 151 if (blob && blob.size > 0 && blob.size <= PREPARED_FILE_SIZE) { 152 var ext = blob.type === 'image/webp' ? '.webp' : '.jpg'; 153 return { blob: blob, name: originalBase + '_prepared' + ext }; 154 } 155 } 156 throw new Error('Ðе ÑдалоÑÑ ÑменÑÑиÑÑ Ð¸Ð·Ð¾Ð±Ñажение до 19 ÐÐ'); 157} 158 159/* ÐÑмеÑÐ°ÐµÑ ÐºÐ½Ð¾Ð¿ÐºÑ Â«Ð¡Ð³ÐµÐ½ÐµÑиÑоваÑÑ» как заблокиÑованнÑÑ. 160 ÐÑÑавлÑем ÐµÑ ÐºÐ»Ð¸ÐºÐ°Ð±ÐµÐ»Ñной, ÑÑÐ¾Ð±Ñ generate() мог показаÑÑ Ð¿ÑиÑинÑ. */ 161function updateBtnState() { 162 var btn = document.getElementById('btn'); 163 if (!btn) return; 164 _imgBlocked = uploadedImages.some(function(img) { return img.status !== 'ok'; }); 165 btn.classList.toggle('is-blocked', _imgBlocked || _ratioRequired); 166 if (!btn.classList.contains('is-generating') && Date.now() >= imageCooldownUntil()) { 167 btn.disabled = false; 168 } 169} 170/* ââââââââââââââââââââââââââââââââ 171 Upload zone 172ââââââââââââââââââââââââââââââââ */ 173var uploadZone = document.getElementById('uploadZone'); 174var fileInput = document.getElementById('fileInput'); 175 176uploadZone.addEventListener('click', function(e) { 177 if (e.target.classList.contains('preview-remove')) return; 178 fileInput.click(); 179}); 180 181fileInput.addEventListener('change', function() { 182 handleFiles(this.files); 183 this.value = ''; 184}); 185 186/* ÐÑделÑÐ½Ð°Ñ ÑÑÑмка оÑновной камеÑой, обÑÑнÑй вÑÐ±Ð¾Ñ Ñайла оÑÑаÑÑÑÑ Ð±ÐµÐ· изменений */ 187(function addImageCameraButton() { 188 if (document.getElementById('imageCameraWrap')) return; 189 190 var cameraWrap = document.createElement('div'); 191 cameraWrap.id = 'imageCameraWrap'; 192 cameraWrap.style.cssText = 'margin-top:10px;text-align:center;'; 193 cameraWrap.innerHTML = 194 '<input id="imageCameraInput" type="file" accept="image/*" capture="environment" style="display:none">' + 195 '<button id="imageCameraButton" class="upload-zone" type="button" style="box-sizing:border-box;width:100%;appearance:none;-webkit-appearance:none;font-family:inherit;color:inherit;">' + 196 '<span class="upload-zone-inner"><span class="upload-icon">📷</span><span class="upload-hint"><strong>Ðли ÑделайÑе ÑоÑо</strong></span></span>' + 197 '</button>'; 198 uploadZone.insertAdjacentElement('afterend', cameraWrap); 199 200 var cameraInput = document.getElementById('imageCameraInput'); 201 document.getElementById('imageCameraButton').addEventListener('click', function() { 202 cameraInput.value = ''; 203 cameraInput.click(); 204 }); 205 cameraInput.addEventListener('change', function() { 206 if (this.files && this.files.length) handleFiles(this.files); 207 this.value = ''; 208 }); 209})(); 210 211uploadZone.addEventListener('dragover', function(e) { 212 e.preventDefault(); 213 this.classList.add('dragover'); 214}); 215uploadZone.addEventListener('dragleave', function() { 216 this.classList.remove('dragover'); 217}); 218uploadZone.addEventListener('drop', function(e) { 219 e.preventDefault(); 220 this.classList.remove('dragover'); 221 handleFiles(e.dataTransfer.files); 222}); 223 224document.addEventListener('paste', function(e) { 225 var clipboard = e.clipboardData; 226 if (!clipboard) return; 227 228 var allowedTypes = ['image/jpeg', 'image/png', 'image/webp']; 229 var images = [];
230 Array.from(clipboard.items || []).forEach(function(item) { 231 if (item.kind !== 'file' || allowedTypes.indexOf(item.type) === -1) return; 232 var file = item.getAsFile(); 233 if (file) images.push(file); 234 }); 235 236 if (!images.length) return; 237 e.preventDefault(); 238 uploadZone.classList.add('dragover'); 239 setTimeout(function() { uploadZone.classList.remove('dragover'); }, 350); 240 handleFiles(images); 241}); 242 243function handleFiles(files) { 244 var MAX = getMaxImages(); 245 var current = uploadedImages.length; 246 var available = MAX - current; 247 248 if (available <= 0) { 249 alert('ÐакÑимÑм ' + MAX + ' изобÑажений Ñже добавлено'); 250 return; 251 } 252 253 var list = Array.from(files); 254 255 if (list.length > available) { 256 list = list.slice(0, available); 257 alert('Ðобавлено ÑолÑко ' + available + ' Ñайла(ов) â доÑÑигнÑÑ Ð»Ð¸Ð¼Ð¸Ñ ' + MAX); 258 } 259 260 list.forEach(function(file) { uploadFile(file); }); 261} 262 263function uploadFile(file) { 264 var id = 'img_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7); 265 var previewSrc = URL.createObjectURL(file); 266 var item = { id: id, previewSrc: previewSrc, kieUrl: null, status: 'uploading', stage: 'uploading', errMsg: '' }; 267 268 uploadedImages.push(item); 269 renderPreviews(); 270 271 // This template is shared by landing pages: only /image opts into preparation. 272 if (/^\/image\/?$/.test(window.location.pathname) && window.SinonimImagePrepare) { 273 item.stage = 'preparing'; 274 renderPreviews(); 275 SinonimImagePrepare.prepare(file, { 276 maxBytes: PREPARED_FILE_SIZE, 277 maxEdge: file.size > MAX_FILE_SIZE ? 4096 : 0, 278 allowResize: file.size > MAX_FILE_SIZE 279 }).then(function(prepared) { 280 if (uploadedImages.indexOf(item) === -1) return; 281 if (!prepared.prepared && file.size > MAX_FILE_SIZE) { 282 item.status = 'error'; 283 item.errMsg = 'ÐÑаÑÐ·ÐµÑ Ð½Ðµ Ñмог подгоÑовиÑÑ Ñайл. ÐÑбеÑиÑе изобÑажение ÑазмеÑом до 20 ÐÐ.'; 284 renderPreviews(); 285 return; 286 } 287 if (prepared.prepared) { 288 URL.revokeObjectURL(item.previewSrc); 289 item.previewSrc = URL.createObjectURL(prepared.blob); 290 } 291 // If Canvas fails, the original reaches the existing server normalizer. 292 item.stage = 'uploading'; 293 renderPreviews(); 294 sendImageFile(item, prepared.blob, prepared.name); 295 }); 296 return; 297 } 298 299 if (file.size > MAX_FILE_SIZE) { 300 item.stage = 'preparing'; 301 renderPreviews(); 302 prepareLargeImage(file).then(function(prepared) { 303 item.stage = 'uploading'; 304 renderPreviews(); 305 sendImageFile(item, prepared.blob, prepared.name); 306 }).catch(function(error) { 307 item.status = 'error'; 308 item.errMsg = error && error.message ? error.message : 'Ðе ÑдалоÑÑ ÑменÑÑиÑÑ Ð¸Ð·Ð¾Ð±Ñажение'; 309 renderPreviews(); 310 }); 311 return; 312 } 313 314 sendImageFile(item, file, file.name || 'image'); 315} 316 317function sendImageFile(item, file, filename) { 318 var fd = new FormData(); 319 fd.append('file', file, filename); 320 321 fetch(SELF + '&action=upload', { method: 'POST', body: fd }) 322 .then(function(r) { return r.json(); }) 323 .then(function(json) { 324 if (json.error) { 325 item.status = 'error'; 326 item.errMsg = json.error; 327 } else { 328 item.kieUrl = json.url; 329 item.status = 'ok'; 330 item.stage = 'ready'; 331 item.savedAt = Date.now(); 332 saveUploads(); 333 scheduleUploadExpiry(item.id, item.savedAt); 334 } 335 renderPreviews(); 336 }) 337 .catch(function() { 338 item.status = 'error'; 339 item.errMsg = 'ÐÑибка ÑеÑи'; 340 renderPreviews(); 341 }); 342} 343 344function removeImage(id) { 345 uploadedImages = uploadedImages.filter(function(img) { return img.id !== id; }); 346 renderPreviews(); 347 saveUploads(); 348} 349 350function refineImage(kieUrl) { 351 if (!kieUrl) return; 352 353 // ÐÑиÑаем вÑе ÑекÑÑие изобÑÐ°Ð¶ÐµÐ½Ð¸Ñ 354 uploadedImages = []; 355 356 // ⚡ ÐоÑабоÑка пÑи минималÑной модеÑаÑии â пеÑеклÑÑаем на бÑÑÑÑÑÑ Ð¼Ð¾Ð´ÐµÐ»Ñ 357 var ns = document.getElementById('nsmode'); 358 var fm = document.getElementById('fastModel'); 359 if (ns && ns.checked && fm) { 360 fm.checked = true; 361 fm.dispatchEvent(new Event('change', { bubbles: true })); 362 var nsWarn = document.getElementById('nsUploadWarn'); 363 if (nsWarn) nsWarn.style.display = 'none'; 364 } 365 366 var id = 'img_ref_' + Date.now() + '_' + Math.random().toString(36).slice(2,
3666); 367 uploadedImages.push({ 368 id: id, 369 previewSrc: sugUrl(kieUrl), 370 kieUrl: kieUrl, 371 status: 'ok' 372 }); 373 renderPreviews(); 374 saveUploads(); 375 document.getElementById('prompt').value = ''; 376 saveForm(); 377 document.getElementById('prompt').scrollIntoView({ behavior: 'smooth', block: 'center' }); 378 setTimeout(function() { document.getElementById('prompt').focus(); }, 400); 379} 380 381function renderPreviews() { 382 var container = document.getElementById('uploadPreviews'); 383 if (!uploadedImages.length) { 384 container.innerHTML = ''; 385 updateBtnState(); 386 return; 387 } 388 389 var html = ''; 390 uploadedImages.forEach(function(img) { 391 html += '<div class="preview-item" id="pi_' + img.id + '">'; 392 html += '<img src="' + img.previewSrc + '" onerror="sugProxyImageFallback(this)" alt="">'; 393 394 if (img.status === 'uploading') { 395 html += '<div class="preview-overlay"' + (img.stage === 'preparing' ? ' style="flex-direction:column;gap:5px"' : '') + '><div class="preview-spinner"></div>'; 396 if (img.stage === 'preparing') html += '<span style="font-size:10px;line-height:1;color:#fff">ÐодгоÑавливаемâ¦</span>'; 397 html += '</div>'; 398 } else if (img.status === 'error') { 399 html += '<div class="preview-error-badge" title="' + escAttr(img.errMsg || 'ÐÑибка') + '">❌</div>'; 400 } 401 402 html += '<button class="preview-remove" onclick="removeImage(\'' + img.id + '\')" title="УдалиÑÑ">×</button>'; 403 html += '</div>'; 404 }); 405 406 container.innerHTML = html; 407 updateBtnState(); 408} 409 410function getReadyImageUrls() { 411 return uploadedImages 412 .filter(function(img) { return img.status === 'ok' && img.kieUrl; }) 413 .map(function(img) { return img.kieUrl; }); 414} 415 416function hasPendingUploads() { 417 return uploadedImages.some(function(img) { return img.status === 'uploading'; }); 418} 419 420/* ÐбÑий ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ Ñакже иÑполÑзÑеÑÑÑ Ð² генеÑаÑоÑе видео. */ 421var imagePromptImprover = window.initPromptImprover({ 422 promptId: 'prompt', 423 buttonId: 'improvePromptBtn', 424 panelId: 'improvePromptPanel', 425 endpoint: SELF + '&action=improve_prompt', 426 target: 'image', 427 hasPendingUploads: hasPendingUploads, 428 getImageUrls: function() { return getReadyImageUrls().slice(0, 5); }, 429 onBalance: function(balance) { 430 var balanceEl = document.querySelector('.zaprosovProNum'); 431 if (balanceEl) balanceEl.textContent = balance; 432 }, 433 onFreeRemaining: function(remaining) { 434 var costEl = document.querySelector('.prompt-improve-cost'); 435 if (costEl) costEl.textContent = 'оÑÑалоÑÑ Ð±ÐµÑплаÑно: ' + remaining; 436 } 437}); 438 439function updateImprovePromptButton() { 440 imagePromptImprover.update(); 441} 442 443/* ââââââââââââââââââââââââââââââââ 444 Persist: ÑоÑма 445ââââââââââââââââââââââââââââââââ */ 446var imageFastPreferenceKey = ''; 447function imageModelSelection() { 448 var selection = {}; 449 ['fastModel', 'textQuality', 'nsmode'].forEach(function(id) { 450 var el = document.getElementById(id); 451 selection[id] = !!(el && el.checked); 452 }); 453 return selection; 454} 455 456function initImageFastPreference() { 457 var config = window.IMAGE_FAST_PREFERENCE; 458 var fm = document.getElementById('fastModel'); 459 if (!config || !config.enabled || !(config.userId > 0) || !fm) return; 460 var key = 'imageFastPreferenceV1:' + config.userId; 461 var selection, first = false; 462 try { 463 var saved = localStorage.getItem(key); 464 if (saved !== null) { 465 selection = JSON.parse(saved); 466 if (!selection || ['fastModel', 'textQuality', 'nsmode'].some(function(id) { return typeof selection[id] !== 'boolean'; })) return; 467 } else { 468 if (!config.eligible) return; 469 selection = {fastModel:true, textQuality:false, nsmode:false}; 470 // ÐÑли нелÑÐ·Ñ ÑÐ¾Ñ ÑаниÑÑ Ð²ÑбоÑ, не вклÑÑаем Ð¼Ð¾Ð´ÐµÐ»Ñ Ð·Ð°Ð½Ð¾Ð²Ð¾ пÑи каждом оÑкÑÑÑии. 471 localStorage.setItem(key, JSON.stringify(selection)); 472 first = true; 473 } 474 } catch(e) { return; } 475 ['fastModel', 'textQuality', 'nsmode'].forEach(function(id) { 476 var el = document.getElementById(id); 477 if (el) el.checked = selection[id]; 478 }); 479 imageFastPreferenceKey = key; 480 var notice = document.getElementById('fastModelAutoNotice'); 481 if (notice) notice.hidden = !first; 482} 483 484function saveForm() { 485 try { 486 localStorage.setItem(PROMPT_KEY, document.getElementById('prompt').value); 487 localStorage.setItem(FORM_KEY, JSON.stringify({ 488 format: document.getElementById('format').value, 489 resolution: document.getElementById('resolution').value, 490 ratio: document.getElementById('ratio').value, 491 fastModel: document.getElementById('fastModel') ? document.getElementById('fastModel').
491checked : false, 492 nsmode: document.getElementById('nsmode') ? document.getElementById('nsmode').checked : false, 493 textQuality: document.getElementById('textQuality') ? document.getElementById('textQuality').checked : false, 494 ruAudience: document.getElementById('ruAudience') ? document.getElementById('ruAudience').checked : true 495 })); 496 } catch(e) {} 497 if (imageFastPreferenceKey) { 498 try { localStorage.setItem(imageFastPreferenceKey, JSON.stringify(imageModelSelection())); } catch(e) {} 499 } 500 updateImprovePromptButton(); 501} 502 503function restoreForm() { 504 var savedPrompt = null; 505 try { savedPrompt = localStorage.getItem(PROMPT_KEY); } catch(e) {} 506 if (savedPrompt !== null) document.getElementById('prompt').value = savedPrompt; 507 508 var state; 509 try { state = JSON.parse(localStorage.getItem(FORM_KEY)); } catch(e) {} 510 if (!state) return; 511 if (savedPrompt === null && state.page === window.location.pathname && state.prompt) { 512 document.getElementById('prompt').value = state.prompt; 513 try { localStorage.setItem(PROMPT_KEY, state.prompt); } catch(e) {} 514 } 515 if (state.format) document.getElementById('format').value = state.format; 516 if (state.resolution) document.getElementById('resolution').value = state.resolution; 517 if (state.ratio) { 518 document.getElementById('ratio').value = state.ratio; 519 document.querySelectorAll('.ratio-pill').forEach(function(p) { 520 p.classList.toggle('active', p.dataset.value === state.ratio); 521 }); 522 } 523 if (state.fastModel !== undefined) { 524 var fm = document.getElementById('fastModel'); 525 if (fm) fm.checked = !!state.fastModel; 526 } 527 if (state.nsmode !== undefined) { 528 var ns = document.getElementById('nsmode'); 529 if (ns) ns.checked = !!state.nsmode && !document.getElementById('goReg'); 530 } 531 if (state.textQuality !== undefined) { 532 var tq = document.getElementById('textQuality'); 533 if (tq) tq.checked = !!state.textQuality; 534 } 535 if (state.ruAudience !== undefined) { 536 var ra = document.getElementById('ruAudience'); 537 if (ra) ra.checked = !!state.ruAudience; 538 } 539} 540 541/* ââââââââââââââââââââââââââââââââ 542 ÐоÑадоÑнÑе ÑÑÑаниÑÑ Ð¸Ð·Ð¾Ð±Ñажений: 543 обÑие Ð¿Ð¾Ð»Ñ Ð¸ пÑеÑеÑÑ Ð±ÐµÐ· копиÑÐ¾Ð²Ð°Ð½Ð¸Ñ JS в каждой ÑÑаÑÑе 544ââââââââââââââââââââââââââââââââ */ 545function initImageLanding() { 546 var cfg = window.imageLandingConfig; 547 var wrap = document.querySelector('.textarea-wrap'); 548 var promptEl = document.getElementById('prompt'); 549 if (!cfg || !wrap || !promptEl || !Array.isArray(cfg.presets) || !cfg.presets.length) return; 550 551 var uploadLabel = document.querySelector('.upload-section .field-label'); 552 if (uploadLabel && cfg.uploadLabel) uploadLabel.textContent = cfg.uploadLabel; 553 554 var panel = document.createElement('div'); 555 panel.className = 'imageLandingPanel'; 556 panel.style.cssText = 'margin-top:12px;'; 557 var fields = {}; 558 559 if (Array.isArray(cfg.fields) && cfg.fields.length) { 560 var fieldsBox = document.createElement('div'); 561 fieldsBox.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin-bottom:12px;padding:12px;border:1px solid #d9deea;border-radius:14px;background:#f8f9ff;'; 562 cfg.fields.forEach(function(field) { 563 var label = document.createElement('label'); 564 label.style.cssText = 'display:block;min-width:0;font-weight:700;color:#30364a;'; 565 label.innerHTML = (field.icon || '') + (field.icon ? ' ' : '') + field.label + (field.required ? ' *' : ''); 566 var input; 567 if (field.type === 'select') { 568 input = document.createElement('select'); 569 (field.options || []).forEach(function(option) { 570 var o = document.createElement('option'); 571 if (typeof option === 'object') { 572 o.value = option.value; 573 o.textContent = option.label; 574 } else { 575 o.value = option; 576 o.textContent = option; 577 } 578 input.appendChild(o); 579 }); 580 } else if (field.type === 'textarea') { 581 input = document.createElement('textarea'); 582 input.rows = field.rows || 2; 583 } else { 584 input = document.createElement('input'); 585 input.type = field.type || 'text'; 586 } 587 input.id = field.id; 588 input.placeholder = field.placeholder || ''; 589 input.value = field.value || ''; 590 input.required = !!field.required; 591 input.style.cssText = 'box-sizing:border-box;width:100%;min-width:0;margin-top:7px;padding:10px 12px;border:1px solid #d9deea;border-radius:10px;background:#fff;color:#202637;font:inherit;'; 592 label.appendChild(input); 593 fieldsBox.appendChild(label); 594 fields[field.id] = input; 595 }); 596 panel.appendChild(fieldsBox); 597 } 598 599 if (cfg.presetsTitle) { 600 var presetTitle = document.createElement('div'); 601 presetTitle.style.cssText = 'margin:2px 0 8px;font-weight:700;color:#30364a;';
602 presetTitle.innerHTML = cfg.presetsTitle; 603 panel.appendChild(presetTitle); 604 } 605 606 var presetsBox = document.createElement('div'); 607 presetsBox.className = 'imageLandingPresets'; 608 presetsBox.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;'; 609 presetsBox.innerHTML = cfg.presets.map(function(item, index) { 610 return '<button type="button" class="ratio-pill" data-landing-preset="' + index + '">' + item[0] + ' ' + item[1] + '</button>'; 611 }).join(''); 612 panel.appendChild(presetsBox); 613 614 if (cfg.hint) { 615 var hint = document.createElement('div'); 616 hint.style.cssText = 'margin-top:9px;font-size:13px;line-height:1.45;color:#667085;'; 617 hint.textContent = cfg.hint; 618 panel.appendChild(hint); 619 } 620 wrap.appendChild(panel); 621 622 var currentPreset = 0; 623 function values() { 624 var result = {}; 625 Object.keys(fields).forEach(function(id) { result[id] = fields[id].value.trim(); }); 626 return result; 627 } 628 function makePrompt() { 629 var preset = cfg.presets[currentPreset]; 630 var vals = values(); 631 if (typeof cfg.promptBuilder === 'function') return cfg.promptBuilder(preset, vals); 632 var text = cfg.basePrompt ? cfg.basePrompt.replace('{preset}', preset[2]) : preset[2]; 633 (cfg.fields || []).forEach(function(field) { 634 if (vals[field.id]) text += ' ' + (field.promptLabel || field.label) + ': ' + vals[field.id] + '.'; 635 }); 636 return text; 637 } 638 function updatePrompt() { 639 promptEl.value = makePrompt(); 640 saveForm(); 641 updateImprovePromptButton(); 642 } 643 function setActive() { 644 presetsBox.querySelectorAll('[data-landing-preset]').forEach(function(button, index) { 645 button.classList.toggle('active', parseInt(button.dataset.landingPreset, 10) === currentPreset); 646 }); 647 } 648 649 presetsBox.addEventListener('click', function(e) { 650 var button = e.target.closest('[data-landing-preset]'); 651 if (!button) return; 652 currentPreset = parseInt(button.dataset.landingPreset, 10) || 0; 653 setActive(); 654 updatePrompt(); 655 }); 656 Object.keys(fields).forEach(function(id) { 657 fields[id].addEventListener(fields[id].tagName === 'SELECT' ? 'change' : 'input', updatePrompt); 658 }); 659 660 window.validateImageLanding = function() { 661 var fieldList = cfg.fields || []; 662 for (var i = 0; i < fieldList.length; i++) { 663 var field = fieldList[i]; 664 if (field.required && !fields[field.id].value.trim()) { 665 setStatus('fail', '⚠️ ÐаполниÑе поле «' + field.label + '».'); 666 fields[field.id].focus(); 667 return false; 668 } 669 } 670 if (cfg.minImages) { 671 var readyCount = uploadedImages.filter(function(image) { return image.status === 'ok'; }).length; 672 if (readyCount < cfg.minImages) { 673 setStatus('fail', '⚠️ ' + (cfg.imageRequiredText || 'ÐобавÑÑе иÑÑ Ð¾Ð´Ð½Ð¾Ðµ изобÑажение.')); 674 document.getElementById('uploadZone').scrollIntoView({behavior:'smooth',block:'center'}); 675 return false; 676 } 677 } 678 return true; 679 }; 680 681 setActive(); 682 if (!promptEl.value.trim()) updatePrompt(); 683 if (cfg.ratio) { 684 var ratioButton = document.querySelector('#ratioPills .ratio-pill[data-value="' + cfg.ratio + '"]'); 685 if (ratioButton) ratioButton.click(); 686 } 687 updateUploadMaxHint(); 688} 689 690/* ââââââââââââââââââââââââââââââââ 691 Persist: акÑÐ¸Ð²Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñа 692ââââââââââââââââââââââââââââââââ */ 693function saveTask(taskId, format, startedAt) { 694 try { localStorage.setItem(TASK_KEY, JSON.stringify({ taskId: taskId, format: format, startedAt: startedAt || Date.now() })); } catch(e) {} 695} 696function loadTask() { 697 try { return JSON.parse(localStorage.getItem(TASK_KEY)); } catch(e) { return null; } 698} 699function clearTask() { 700 try { localStorage.removeItem(TASK_KEY); } catch(e) {} 701} 702 703function formatImageElapsed(seconds) { 704 seconds = Math.max(0, Math.floor(seconds || 0)); 705 if (seconds < 60) return seconds + ' ' + pluralImageTime(seconds, 'ÑекÑнда', 'ÑекÑндÑ', 'ÑекÑнд'); 706 var minutes = Math.floor(seconds / 60); 707 var rest = seconds % 60; 708 return minutes + ' ' + pluralImageTime(minutes, 'минÑÑа', 'минÑÑÑ', 'минÑÑ') + (rest ? ' ' + rest + ' Ñек.' : ''); 709} 710 711function pluralImageTime(number, one, few, many) { 712 var n = Math.abs(number) % 100; 713 var n1 = n % 10; 714 if (n > 10 && n < 20) return many; 715 if (n1 > 1 && n1 < 5) return few; 716 if (n1 === 1) return one; 717 return many; 718} 719 720function estimatedImageProgress(seconds) { 721 var value; 722 if (seconds < 10) value = 4 + seconds * 2.1; 723 else if (seconds < 35) value = 25 + (seconds - 10) * 1.5; 724 else if (seconds < 75) value = 62 + (seconds - 35) * .55; 725 else if (seconds < 120) value = 84 + (seconds - 75) * .2; 726 else value = 93 + (seconds - 120) * .05; 727 if (imageProgressServerState === 'waiting' || imageProgressServerState === 'queuing') value = Math.min(value, 58); 728 return Math.min(97, Math.max(4, Math.round(value))); 729} 730 731function imageProgressStage(seconds) { 732 if (imageProgressServerState === 'waiting' || imageProgressServerState === 'queuing') return 'ÐдÑм оÑÐ²ÐµÑ Ð½ÐµÐ¹ÑоÑеÑиâ¦'; 733 if (imageProgressServerState === 'retrying') return 'ÐеÑеклÑÑаемÑÑ Ð½Ð° бÑÑÑÑÑÑ Ð¼Ð¾Ð´ÐµÐ»Ñâ¦'; 734 if (seconds < 10) return 'ÐеÑедаÑм опиÑание нейÑоÑеÑиâ¦'; 735 if (seconds < 35) return 'ÐодбиÑаем композиÑиÑâ¦'; 736 if (seconds < 75) return 'СоздаÑм изобÑажение и мелкие деÑалиâ¦'; 737 if (seconds < 100) return 'УлÑÑÑаем каÑеÑÑво ÑезÑлÑÑаÑаâ¦'; 738 return 'ÐоÑÑи законÑили â ÑÐ¾Ñ ÑанÑем ÑезÑлÑÑаÑâ¦'; 739} 740 741function updateImageProgress() { 742 if (!imageProgressStartedAt) return; 743 var seconds = Math.max(0, Math.floor((Date.now() - imageProgressStartedAt) / 1000)); 744 var percent = estimatedImageProgress(seconds); 745 var percentEl = document.getElementById('statusPercent'); 746 var fill = document.getElementById('statusProgressFill');
747 var elapsed = document.getElementById('statusElapsed'); 748 var stage = document.getElementById('statusStage'); 749 if (percentEl) percentEl.textContent = percent + '%'; 750 if (fill) fill.style.width = percent + '%'; 751 if (elapsed) elapsed.textContent = 'ÐÑоÑло ' + formatImageElapsed(seconds); 752 if (stage) stage.textContent = imageProgressStage(seconds); 753} 754 755function setGenerateButtonBusy(busy) { 756 var btn = document.getElementById('btn'); 757 var label = document.getElementById('btnLabel'); 758 if (!btn || !label) return; 759 btn.classList.toggle('is-generating', !!busy); 760 label.textContent = busy ? 'СоздаÑм изобÑажениеâ¦' : 'СгенеÑиÑоваÑÑ'; 761} 762 763function startImageProgress(startedAt) { 764 clearInterval(imageProgressTimer); 765 imageProgressStartedAt = Number(startedAt) || Date.now(); 766 imageProgressServerState = 'starting'; 767 var bar = document.getElementById('statusBar'); 768 if (bar) { 769 bar.classList.remove('is-simple', 'is-ok', 'is-fail'); 770 bar.classList.add('is-running'); 771 bar.removeAttribute('data-progress-scrolled'); 772 } 773 updateImageProgress(); 774 imageProgressTimer = setInterval(updateImageProgress, 1000); 775 setGenerateButtonBusy(true); 776} 777 778function setImageProgressServerState(state) { 779 imageProgressServerState = state || 'generating'; 780 updateImageProgress(); 781} 782 783function finishImageProgress(success) { 784 clearInterval(imageProgressTimer); 785 imageProgressTimer = null; 786 if (!imageProgressStartedAt) return; 787 if (success) { 788 var percentEl = document.getElementById('statusPercent'); 789 var fill = document.getElementById('statusProgressFill'); 790 var stage = document.getElementById('statusStage'); 791 if (percentEl) percentEl.textContent = '100%'; 792 if (fill) fill.style.width = '100%'; 793 if (stage) stage.textContent = 'ÐоÑово â изобÑажение ÑÐ¾Ñ Ñанено.'; 794 } 795} 796 797/* ââââââââââââââââââââââââââââââââ 798 Persist: ÑезÑлÑÑÐ°Ñ 799ââââââââââââââââââââââââââââââââ */ 800function saveResult(imageUrl, format, ts, taskId) { 801 try { localStorage.setItem(IMG_KEY, JSON.stringify({ imageUrl: imageUrl, format: format, ts: ts || Date.now(), taskId: taskId || '' })); } catch(e) {} 802} 803function clearResult() { 804 try { localStorage.removeItem(IMG_KEY); } catch(e) {} 805 document.getElementById('result').innerHTML = ''; 806} 807function restoreResult() { 808 var saved; 809 try { saved = JSON.parse(localStorage.getItem(IMG_KEY)); } catch(e) {} 810 if (!saved || !saved.imageUrl) return; 811 showResult(saved.imageUrl, saved.format || 'jpg', saved.ts, true, saved.taskId || ''); 812} 813 814/* ââââââââââââââââââââââââââââââââ 815 Ratio pills 816ââââââââââââââââââââââââââââââââ */ 817document.getElementById('ratioPills').addEventListener('click', function(e) { 818 var pill = e.target.closest('.ratio-pill'); 819 if (!pill) return; 820 this.querySelectorAll('.ratio-pill').forEach(function(p) { p.classList.remove('active'); }); 821 pill.classList.add('active'); 822 document.getElementById('ratio').value = pill.dataset.value; 823 applyModelConstraints(); 824 saveForm(); 825}); 826 827document.getElementById('resolution').addEventListener('change', function() { 828 var ns = document.getElementById('nsmode'); 829 if (ns && ns.checked && this.value !== '1K') { 830 this.value = '1K'; 831 } 832 applyModelConstraints(); 833 saveForm(); 834}); 835document.getElementById('prompt').addEventListener('input', saveForm); 836document.getElementById('format').addEventListener('change', saveForm); 837document.getElementById('fastModel').addEventListener('change', saveForm); 838document.getElementById('nsmode').addEventListener('change', saveForm); 839document.getElementById('ruAudience').addEventListener('change', saveForm); 840 841document.getElementById('textQuality').addEventListener('change', function() { 842 if (this.checked) { 843 document.getElementById('fastModel').checked = false; 844 document.getElementById('nsmode').checked = false;
845 } 846 updateUploadMaxHint(); 847 updateNsUploadWarn(); 848 applyModelConstraints(); 849 saveForm(); 850}); 851 852function updateBtnCost() { 853 var res = document.getElementById('resolution').value; 854 var cost = COSTS[res] || 8; 855 document.getElementById('btnCost').textContent = 'за ' + cost + ' PRO-баллов'; 856} 857 858/* ââââââââââââââââââââââââââââââââ 859 ÐгÑаниÑÐµÐ½Ð¸Ñ Ð¼Ð¾Ð´ÐµÐ»ÐµÐ¹: блокиÑÑем недоÑÑÑпнÑе 860 комбинаÑии «ÑооÑноÑение + ÑазÑеÑение» 861ââââââââââââââââââââââââââââââââ */ 862var RES_ORDER = { '1K': 1, '2K': 2, '4K': 3 }; 863/* GPT Image 2.5 Sunburst: 5:4 and 4:5 are unavailable in Kie. 864 Keep auto at 1K so the direct OpenAI fallback can honor explicit 2K/4K sizes. */ 865var GPT_MAX_RES = { 'auto': '1K', '5:4': null, '4:5': null }; 866function gptRatioSupportsResolution(ratio, resolution) { 867 var limit = Object.prototype.hasOwnProperty.call(GPT_MAX_RES, ratio) ? GPT_MAX_RES[ratio] : '4K'; 868 return limit !== null && RES_ORDER[resolution] <= RES_ORDER[limit]; 869} 870/* wan/2-7-image-pro: Ð¿Ð¾Ð´Ð¼ÐµÐ½Ñ ÑооÑноÑений на бÑкенде */ 871var WAN_RATIO_MAP = { 'auto': '1:1', '3:2': '4:3', '2:3': '3:4', '5:4': '4:3', '4:5': '3:4' }; 872 873function applyModelConstraints() { 874 var tq = document.getElementById('textQuality').checked; 875 var fm = document.getElementById('fastModel').checked; 876 var ns = document.getElementById('nsmode').checked; 877 var resEl = document.getElementById('resolution'); 878 var ratioEl = document.getElementById('ratio'); 879 var ratio = ratioEl.value; 880 var pills = document.querySelectorAll('#ratioPills .ratio-pill'); 881 var hint = document.getElementById('ratioHint'); 882 883 /* ÑбÑÐ¾Ñ */ 884 Array.from(resEl.options).forEach(function(o) { o.disabled = false; }); 885 pills.forEach(function(p) { p.disabled = false; }); 886 hint.style.display = 'none'; 887 _ratioRequired = false; 888 889 if (!tq && !ratio) { 890 ratio = 'auto'; 891 ratioEl.value = ratio; 892 pills.forEach(function(p) { p.classList.toggle('active', p.dataset.value === ratio); }); 893 } 894 895 if (ns) { 896 /* qwen: ÑолÑко 1K (дÑблиÑÑÐµÑ ÑÑÑеÑÑвÑÑÑÑÑ Ð»Ð¾Ð³Ð¸ÐºÑ nsmode) */ 897 resEl.value = '1K'; 898 Array.from(resEl.options).forEach(function(o) { o.disabled = o.value !== '1K'; }); 899 } else if (tq) { 900 var res = resEl.value; 901 902 /* ÐÑÑокое ÑазÑеÑение вÑбиÑаеÑÑÑ Ð¿ÐµÑвÑм. ÐÑли ÑекÑÑее ÑооÑноÑение 903 неÑовмеÑÑимо, Ñнимаем вÑÐ±Ð¾Ñ Ð¸ пÑоÑим вÑбÑаÑÑ ÑÑоÑÐ¾Ð½Ñ Ð²ÑÑÑнÑÑ. */ 904 if (ratio && !gptRatioSupportsResolution(ratio, res)) { 905 ratioEl.value = ''; 906 ratio = ''; 907 pills.forEach(function(p) { p.classList.remove('active'); }); 908 } 909 910 /* Unsupported ratios remain unavailable even at 1K. Keep the user's 911 resolution and ask for a new ratio instead of silently substituting it. */ 912 pills.forEach(function(p) { 913 p.disabled = !gptRatioSupportsResolution(p.dataset.value, res); 914 }); 915 916 _ratioRequired = !ratio; 917 if (_ratioRequired) { 918 hint.innerHTML = 'ÐÐ»Ñ ÑазÑеÑÐµÐ½Ð¸Ñ ' + res + ' вÑбеÑиÑе поддеÑживаемое ÑооÑноÑение ÑÑоÑон вÑÑÑнÑÑ.'; 919 hint.style.display = 'block'; 920 } 921 } else if (fm && WAN_RATIO_MAP[ratio]) { 922 hint.innerHTML = 'ÐÑÑÑÑÐ°Ñ Ð¼Ð¾Ð´ÐµÐ»Ñ Ð½Ðµ поддеÑÐ¶Ð¸Ð²Ð°ÐµÑ ÑооÑноÑение ' + 923 (ratio === 'auto' ? '«ÐвÑо»' : ratio) + 924 ' — изобÑажение бÑÐ´ÐµÑ Ñоздано в ' + WAN_RATIO_MAP[ratio] + '.'; 925 hint.style.display = 'block'; 926 } else if (!ratio) { 927 ratioEl.value = 'auto'; 928 pills.forEach(function(p) { p.classList.toggle('active', p.dataset.value === 'auto'); }); 929 } 930 updateBtnCost(); 931 updateBtnState(); 932} 933 934/* ââââââââââââââââââââââââââââââââ 935 Polling 936ââââââââââââââââââââââââââââââââ */ 937var imageStatusRequests = {}; 938var imageSeedreamDecisions = {}; 939function requestImageStatus(taskId) { 940 // Active-task and history polling share one request and one confirmation dialog. 941 if (imageStatusRequests[taskId]) return imageStatusRequests[taskId]; 942 var request = (async function() { 943 var url = SELF + '&action=status&taskId=' + encodeURIComponent(taskId); 944 var send = async function() { 945 var options; 946 if (imageSeedreamDecisions[taskId]) { 947 var body = new FormData(); 948 body.append('seedream_2k', imageSeedreamDecisions[taskId]); 949 options = {method:'POST', body:body}; 950 } 951 return (await fetch(url, options)).json(); 952 }; 953 var data = await send(); 954 if (data.state === 'confirmation_required' && data.confirmation === 'seedream_2k') { 955 if (!imageSeedreamDecisions[taskId]) { 956 var pausedAt = Date.now(); 957 imageSeedreamDecisions[taskId] = confirm(data.message) ? 'yes' : 'no'; 958 var active = loadTask(); 959 if (active && active.taskId === taskId) { 960 saveTask(taskId, active.format, active.startedAt + Date.now() - pausedAt); 961 } 962 } 963 data = await send(); 964 } 965 return data; 966 })(); 967 imageStatusRequests[taskId] = request; 968 var clear = function() { delete imageStatusRequests[taskId]; }; 969 request.then(clear, clear); 970 return request; 971} 972 973function startPolling(taskId, format) { 974 var version=++imagePollVersion; 975 clearInterval(pollTimer); 976 977 pollTimer = setInterval(async function() { 978 if(version!==imagePollVersion) return; 979 var task = loadTask(); 980 if (task && (Date.now() - task.startedAt) > TASK_TTL) { 981 clearInterval(pollTimer); 982 clearTask(); 983 setStatus('fail', '❌ ÐÑевÑÑено вÑÐµÐ¼Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ. ÐопÑобÑйÑе Ñнова.'); 984 enableBtn(); 985 return; 986 } 987 988 try { 989 var data = await requestImageStatus(taskId); 990 if(version!==imagePollVersion) return; 991 992 if (data.error) { 993 clearInterval(pollTimer); 994 clearTask(); 995 setStatus('fail', '❌ ' + data.error); 996 enableBtn(); 997 return; 998 } 999 1000 if (data.state === 'success' && data.imageUrl) { 1001 clearInterval(pollTimer); 1002 clearTask(); 1003 var ts = Date.now(); 1004 saveResult(data.imageUrl, format, ts, taskId); 1005 setStatus('ok', '✓ ÐзобÑажение гоÑово'); 1006 showResult(data.imageUrl, format, ts, false, taskId); 1007 notifyImageReadyOnce(); 1008 enableBtn(); 1009 1010 } else if (data.state === 'retrying' && data.retryTaskId) { 1011 /* ÐеÑÐ²Ð°Ñ Ð¼Ð¾Ð´ÐµÐ»Ñ Ð½Ð°ÑÑÑила полиÑÐ¸ÐºÑ â пеÑеклÑÑаемÑÑ Ð½Ð° запаÑнÑÑ */ 1012 clearInterval(pollTimer); 1013 var activeTask = loadTask(); 1014 saveTask(data.retryTaskId, format, activeTask && activeTask.startedAt ? activeTask.startedAt : imageProgressStartedAt); 1015 setImageProgressServerState('retrying'); 1016 setStatus('running', '⚙ ÐовÑоÑÐ½Ð°Ñ Ð³ÐµÐ½ÐµÑаÑиÑ...'); 1017 startPolling(data.retryTaskId, format); 1018 1019 } else if (data.state === 'failed') { 1020 clearInterval(pollTimer); 1021 clearTask(); 1022 setStatus('fail', '❌
1022' + (data.failMsg || 'ÐÑибка генеÑаÑии')); 1023 enableBtn(); 1024 1025 } else { 1026 var labels = { waiting: '⏳ РоÑеÑеди...', queuing: '⏳ РоÑеÑеди...', generating: '⚙ ÐенеÑаÑиÑ...', retrying: '⚙ ÐовÑоÑÐ½Ð°Ñ Ð³ÐµÐ½ÐµÑаÑиÑ...' }; 1027 setImageProgressServerState(data.state); 1028 setStatus('running', labels[data.state] || '⏳ ' + data.state); 1029 } 1030 } catch(e) {} 1031 }, POLL_MS); 1032} 1033 1034function tryResumeTask() { 1035 var task = loadTask(); 1036 if (!task || !task.taskId) return false; 1037 if ((Date.now() - task.startedAt) > TASK_TTL) { clearTask(); return false; } 1038 var elapsed = Math.round((Date.now() - task.startedAt) / 1000); 1039 startImageProgress(task.startedAt); 1040 setImageProgressServerState('waiting'); 1041 setStatus('running', '⏳ ÐозобновлÑем ожидание... (Ñже ' + elapsed + ' Ñ)'); 1042 document.getElementById('btn').disabled = true; 1043 _btnEnabledAt = Math.max(_btnEnabledAt, task.startedAt + 20000); 1044 startPolling(task.taskId, task.format || 'jpg'); 1045 enableBtn(); 1046 return true; 1047} 1048 1049/* ââââââââââââââââââââââââââââââââ 1050 Init 1051ââââââââââââââââââââââââââââââââ */ 1052restoreForm(); 1053initImageFastPreference(); 1054initImageLanding(); 1055updateImprovePromptButton(); 1056applyModelConstraints(); 1057updateNsUploadWarn(); 1058 1059(function() { 1060 var ns = document.getElementById('nsmode'); 1061 if (ns && ns.checked) { 1062 var resEl = document.getElementById('resolution'); 1063 resEl.value = '1K'; 1064 Array.from(resEl.options).forEach(function(o) { o.disabled = o.value !== '1K'; }); 1065 updateBtnCost(); 1066 } 1067})(); 1068 1069(function() { 1070 var tq = document.getElementById('textQuality'); 1071 if (tq && tq.checked) { 1072 updateBtnCost(); 1073 } 1074})(); 1075 1076// Ð¡Ð¸Ð½Ñ ÑонизиÑÑем hint Ñ Ð²Ð¾ÑÑÑановленнÑм ÑоÑÑоÑнием fastModel 1077(function() { 1078 updateUploadMaxHint(); 1079})(); 1080var puReady = fetch('/data.php?pu') 1081 .then(function(r){ return r.text(); }) 1082 .then(function(t){ isPaidUser = (t.trim() === '1'); }) 1083 .catch(function(){}); 1084 1085puReady.then(function() { 1086 restoreUploads(); 1087 if (!tryResumeTask()) restoreResult(); 1088}); 1089 1090/* ââââââââââââââââââââââââââââââââ 1091 Done-звÑк (по аналогии Ñ Ð³ÐµÐ½ÐµÑаÑоÑом ÑÑеков) 1092ââââââââââââââââââââââââââââââââ */ 1093var doneAudioCtx = null; 1094var doneAudioUnlocked = false; 1095 1096function initDoneAudioFromGesture() { 1097 try { 1098 var AC = window.AudioContext || window.webkitAudioContext; 1099 if (!AC) return; 1100 if (!doneAudioCtx) doneAudioCtx = new AC(); 1101 if (doneAudioCtx.state === 'suspended') { 1102 doneAudioCtx.resume() 1103 .then(function() { doneAudioUnlocked = true; }) 1104 .catch(function() {}); 1105 } else { 1106 doneAudioUnlocked = true; 1107 } 1108 } catch(e) {} 1109} 1110 1111function playDoneSound() { 1112 try { 1113 if (!doneAudioCtx || !doneAudioUnlocked) return; 1114 var ctx = doneAudioCtx; 1115 var now = ctx.currentTime; 1116 1117 function beep(freq, start, dur) { 1118 var o = ctx.createOscillator(); 1119 var g = ctx.createGain(); 1120 o.type = 'triangle'; 1121 o.frequency.setValueAtTime(freq, start); 1122 g.gain.setValueAtTime(0.0001, start); 1123 g.gain.exponentialRampToValueAtTime(0.8, start + 0.01); 1124 g.gain.exponentialRampToValueAtTime(0.0001, start + dur); 1125 o.connect(g); 1126 g.connect(ctx.destination); 1127 o.start(start); 1128 o.stop(start + dur + 0.02); 1129 } 1130 beep(880, now, 0.18); 1131 beep(1320, now + 0.20, 0.22); 1132 alert('ÐзобÑажение гоÑово'); 1133 } catch(e) {} 1134} 1135 1136var _imageSoundPlayed = false; 1137 1138function notifyImageReadyOnce() { 1139 if (_imageSoundPlayed) return; 1140 _imageSoundPlayed = true; 1141 playDoneSound(); 1142} 1143 1144/* ââââââââââââââââââââââââââââââââ 1145 ÐенеÑаÑÐ¸Ñ 1146ââââââââââââââââââââââââââââââââ */ 1147async function generate() { 1148 if (Date.now() < imageCooldownUntil()) { enableBtn(); return; } 1149 initDoneAudioFromGesture(); // â добавиÑÑ Ð¿ÐµÑвой ÑÑÑокой 1150 _imageSoundPlayed = false; // â ÑбÑаÑÑваем Ñлаг Ð´Ð»Ñ Ð½Ð¾Ð²Ð¾Ð¹ генеÑаÑии 1151 if (_ratioRequired) { 1152 alert('СнаÑала вÑбеÑиÑе поддеÑживаемое ÑооÑноÑение ÑÑоÑон.'); 1153 return; 1154 } 1155 if (window.validateImageLanding && !window.validateImageLanding()) return; 1156 var prompt = document.getElementById('prompt').value.trim(); 1157 if (!prompt) { document.getElementById('prompt').focus(); return; } 1158 1159 if (hasPendingUploads()) { 1160 setStatus('running', '⏱ Ðжидаем завеÑÑÐµÐ½Ð¸Ñ Ð·Ð°Ð³ÑÑзки изобÑажений...'); 1161 return; 1162 } 1163 1164 var failedImage = uploadedImages.find(function(img) { return img.status === 'error'; }); 1165 if (failedImage) { 1166 setStatus('fail', '❌ Ðе ÑдалоÑÑ Ð¿Ð¾Ð´Ð³Ð¾ÑовиÑÑ Ð¸Ð·Ð¾Ð±Ñажение: ' + escHtml(failedImage.errMsg || 'неизвеÑÑÐ½Ð°Ñ Ð¾Ñибка') + '. УдалиÑе его кнопкой × Ð¸ попÑобÑйÑе Ñнова.'); 1167 return; 1168 } 1169 1170 ++imagePollVersion; // Ignore late responses from the previous generation.
1171 clearInterval(pollTimer); 1172 clearTask(); 1173 document.getElementById('result').innerHTML = ''; 1174 1175 var format = document.getElementById('format').value; 1176 var resolution = document.getElementById('resolution').value; 1177 var ratio = document.getElementById('ratio').value; 1178 var imageUrls = getReadyImageUrls(); 1179 1180 var generationStartedAt = Date.now(); 1181 startImageProgress(generationStartedAt); 1182 setStatus('running', '⏱ ÐÑпÑавка задаÑи...'); 1183 document.getElementById('btn').disabled = true; 1184 _btnEnabledAt = Date.now() + 20000; 1185 1186 var fd = new FormData(); 1187 fd.append('prompt', prompt); 1188 fd.append('format', format); 1189 fd.append('resolution', resolution); 1190 fd.append('ratio', ratio); 1191 var fastModel = document.getElementById('fastModel'); 1192 if (fastModel && fastModel.checked) { 1193 fd.append('fast_model', '1'); 1194 } 1195 var nsmode = document.getElementById('nsmode'); 1196 if (nsmode && nsmode.checked) { 1197 fd.append('ns_mode', '1'); 1198 } 1199 1200 var ruAudience = document.getElementById('ruAudience'); 1201 if (ruAudience && ruAudience.checked) { 1202 fd.append('ru_audience', '1'); 1203 } 1204 1205 var textQuality = document.getElementById('textQuality'); 1206 if (textQuality && textQuality.checked) { 1207 fd.append('text_quality', '1'); 1208 } 1209 1210 if (imageUrls.length > 0) { 1211 fd.append('image_urls', JSON.stringify(imageUrls)); 1212 } 1213 1214 try { 1215 var res = await fetch(SELF + '&action=create', { method: 'POST', body: fd }); 1216 var json = await res.json(); 1217 1218 if (json.state === 'confirmation_required' && json.confirmation === 'seedream_2k') { 1219 if (!confirm(json.message)) { 1220 setStatus('fail', 'ÐеÑÐµÑ Ð¾Ð´ на 2K оÑменÑн. PRO-Ð±Ð°Ð»Ð»Ñ Ð½Ðµ ÑпиÑанÑ.'); 1221 enableBtn(); 1222 return; 1223 } 1224 fd.append('seedream_2k', 'yes'); 1225 generationStartedAt = Date.now(); 1226 startImageProgress(generationStartedAt); 1227 res = await fetch(SELF + '&action=create', {method:'POST', body:fd}); 1228 json = await res.json(); 1229 } 1230 1231 if (json.retry_after) _btnEnabledAt = Math.max(_btnEnabledAt, Date.now() + Number(json.retry_after) * 1000); 1232 if (json.error) { 1233 setStatus('fail', ' ' + json.error); 1234 enableBtn(); 1235 return; 1236 } 1237 1238 saveTask(json.taskId, format, generationStartedAt); 1239 setImageProgressServerState('waiting'); 1240 setStatus('running', '⏳ ÐадаÑа пÑинÑÑа, ожидаем ÑезÑлÑÑаÑ...'); 1241 startPolling(json.taskId, format); 1242 enableBtn(); // A new request is allowed after the server-wide 20-second interval. 1243 1244 } catch(e) { 1245 setStatus('fail', '❌ ÐÑибка ÑеÑи'); 1246 enableBtn(); 1247 } 1248} 1249 1250/* ââââââââââââââââââââââââââââââââ 1251 UI helpers 1252ââââââââââââââââââââââââââââââââ */ 1253function enableBtn() { 1254 clearTimeout(imageCooldownTimer); 1255 var delay = Math.max(0, imageCooldownUntil() - Date.now()); 1256 var btn = document.getElementById('btn'); 1257 function finishEnable() { 1258 if (Date.now() < imageCooldownUntil()) { enableBtn(); return; } 1259 btn.disabled = false; 1260 setGenerateButtonBusy(false); 1261 updateBtnState(); 1262 } 1263 if (delay === 0) { finishEnable(); return; } 1264 btn.disabled = true; 1265 imageCooldownTimer = setTimeout(finishEnable, delay); 1266} 1267 1268function sugUrl(img){ return 'https://a.sinonim.org/sug.php?img=' + encodeURIComponent(img) + (isPaidUser ? '&paidUser' : ''); } 1269 1270function sugDow(img){ return 'https://a.sinonim.org/sug.php?dow&img=' + encodeURIComponent(img) + (isPaidUser ? '&paidUser' : ''); } 1271 1272function imageOutputPresetButton(img, className) { 1273 var preset = window.imageOutputPreset; 1274 if (!preset || !img) return ''; 1275 var label = preset.buttonHtml || '📥 СкаÑаÑÑ Ð¿Ð¾Ð´Ð³Ð¾Ñовленное изобÑажение'; 1276 var style = 'cursor:pointer;font-family:inherit' + (preset.enabled === false ? ';display:none' : ''); 1277 return '<button type="button" class="' + className + ' image-output-preset-btn image-download-control" style="' + style + 1278 '" onclick="downloadImageOutputPreset(this)">' + label + '</button>'; 1279} 1280 1281function loadImageFromResult(src) { 1282 return new Promise(function(resolve, reject) { 1283 var image = new Image(); 1284 image.onload = function() { resolve(image); }; 1285 image.onerror = function() { reject(new Error('Ðе ÑдалоÑÑ Ð·Ð°Ð³ÑÑзиÑÑ Ð¸Ð·Ð¾Ð±Ñажение')); }; 1286 image.src = src; 1287 }); 1288} 1289 1290function imageOutputPresetSource(button) { 1291 var container = button.closest('.hist-card') || button.closest('#result'); 1292 var preview = container ? container.querySelector('img') : null; 1293 if (!preview) throw new Error('Ðе ÑдалоÑÑ Ð½Ð°Ð¹Ñи изобÑажение ÑезÑлÑÑаÑа');
1294 if (preview.complete && preview.naturalWidth) return Promise.resolve(preview); 1295 1296 var src = preview.currentSrc || preview.src || ''; 1297 if (!src) throw new Error('Ðе ÑдалоÑÑ Ð½Ð°Ð¹Ñи адÑÐµÑ Ð¸Ð·Ð¾Ð±ÑÐ°Ð¶ÐµÐ½Ð¸Ñ ÑезÑлÑÑаÑа'); 1298 return loadImageFromResult(src); 1299} 1300 1301async function outputPresetBlob(canvas, preset) { 1302 var quality = Number(preset.quality) || 0.92; 1303 var mime = preset.mime || 'image/jpeg'; 1304 var maxBytes = Number(preset.maxBytes) || 0; 1305 var blob = null; 1306 1307 for (var i = 0; i < 5; i++) { 1308 blob = await canvasToImageBlob(canvas, mime, quality); 1309 if (!blob || !blob.size) throw new Error('Ðе ÑдалоÑÑ Ð¿Ð¾Ð´Ð³Ð¾ÑовиÑÑ Ñайл'); 1310 if (!maxBytes || blob.size <= maxBytes) return blob; 1311 quality = Math.max(0.55, quality - 0.10); 1312 } 1313 throw new Error('Ðе ÑдалоÑÑ ÑменÑÑиÑÑ Ñайл до допÑÑÑимого ÑазмеÑа'); 1314} 1315 1316// Ten-second repeat-download cooldown. This is not file-transfer progress. 1317var imageDownloadStates = new WeakMap(); 1318 1319function beginImageDownload(button) { 1320 var existing = imageDownloadStates.get(button); 1321 if (existing) { 1322 if (existing.doneAt && Date.now() >= Math.max(existing.unlockAt, existing.doneAt + 4000)) existing.reset(); 1323 else return null; 1324 } 1325 var original = { 1326 html: button.innerHTML, 1327 disabled: button.disabled, 1328 ariaDisabled: button.getAttribute('aria-disabled'), 1329 ariaBusy: button.getAttribute('aria-busy') 1330 }; 1331 var state = {unlockAt: Date.now() + 10000, doneAt: 0, timer: null}; 1332 imageDownloadStates.set(button, state); 1333 button.classList.add('image-download-busy'); 1334 button.setAttribute('aria-disabled', 'true'); 1335 button.setAttribute('aria-busy', 'true'); 1336 if ('disabled' in button) button.disabled = true; 1337 button.innerHTML = '<span class="image-download-fill" aria-hidden="true"></span>' + 1338 '<span class="image-download-label" role="status" aria-live="polite"></span>'; 1339 var label = button.querySelector('.image-download-label'); 1340 1341 state.reset = function() { 1342 clearInterval(state.timer); 1343 imageDownloadStates.delete(button); 1344 button.classList.remove('image-download-busy', 'image-download-started'); 1345 button.innerHTML = original.html; 1346 if ('disabled' in button) button.disabled = original.disabled; 1347 ['aria-disabled', 'aria-busy'].forEach(function(name) { 1348 var value = name === 'aria-disabled' ? original.ariaDisabled : original.ariaBusy; 1349 if (value === null) button.removeAttribute(name); else button.setAttribute(name, value); 1350 }); 1351 }; 1352 function update() { 1353 if (!button.isConnected) { state.reset(); return; } 1354 var now = Date.now(); 1355 if (state.doneAt && now >= Math.max(state.unlockAt, state.doneAt + 4000)) { state.reset(); return; } 1356 var started = state.doneAt && now < state.doneAt + 4000; 1357 button.classList.toggle('image-download-started', !!started); 1358 var text = started ? 'СкаÑивание наÑалоÑÑ' : state.doneAt 1359 ? 'Файл бÑÐ´ÐµÑ Ð² загÑÑÐ·ÐºÐ°Ñ ' 1360 : 'ÐодгоÑавливаем Ñайлâ¦'; 1361 if (label.textContent !== text) label.textContent = text; 1362 } 1363 state.started = function() { 1364 state.doneAt = Date.now(); 1365 button.setAttribute('aria-busy', 'false'); 1366 update(); 1367 }; 1368 state.timer = setInterval(update, 100); 1369 update(); 1370 return state; 1371} 1372 1373function imageDownloadClick(event, button) { 1374 var state = beginImageDownload(button); 1375 if (!state) { if (event) event.preventDefault(); return false; } 1376 try { 1377 // Preserve the existing download URL and local-proxy fallback. 1378 var result = sugProxyNavigate(event, button, function(url) { 1379 var frame = document.createElement('iframe'); 1380 frame.hidden = true; 1381 frame.title = 'СкаÑивание изобÑажениÑ'; 1382 frame.setAttribute('aria-hidden', 'true'); 1383 frame.src = url; 1384 document.body.appendChild(frame); 1385 setTimeout(function() { frame.remove(); }, 600000); 1386 }); 1387 state.started(); 1388 return result; 1389 } catch (e) { 1390 state.reset(); 1391 if (event) event.preventDefault(); 1392 return false; 1393 } 1394} 1395 1396async function downloadImageOutputPreset(button) { 1397 var preset = window.imageOutputPreset; 1398 if (!preset || preset.enabled === false || !button || button.disabled) return; 1399 1400 var width = parseInt(preset.width, 10); 1401 var height = parseInt(preset.height, 10); 1402 if (!width || !height) { 1403 alert('Ðе Ð·Ð°Ð´Ð°Ð½Ñ ÑазмеÑÑ Ð¿Ð¾Ð´Ð³Ð¾Ñовленного изобÑажениÑ.'); 1404 return; 1405 } 1406 1407 var downloadState = beginImageDownload(button); 1408 if (!downloadState) return; 1409 1410 try { 1411 var image = await imageOutputPresetSource(button);
1412 var sourceWidth = image.naturalWidth || image.width; 1413 var sourceHeight = image.naturalHeight || image.height; 1414 var targetRatio = width / height; 1415 var sourceRatio = sourceWidth / sourceHeight; 1416 var sx = 0; 1417 var sy = 0; 1418 var sw = sourceWidth; 1419 var sh = sourceHeight; 1420 1421 if (sourceRatio > targetRatio) { 1422 sw = sourceHeight * targetRatio; 1423 sx = (sourceWidth - sw) / 2; 1424 } else if (sourceRatio < targetRatio) { 1425 sh = sourceWidth / targetRatio; 1426 sy = (sourceHeight - sh) / 2; 1427 } 1428 1429 var canvas = document.createElement('canvas'); 1430 canvas.width = width; 1431 canvas.height = height; 1432 var ctx = canvas.getContext('2d'); 1433 if (!ctx) throw new Error('ÐÑаÑÐ·ÐµÑ Ð½Ðµ поддеÑÐ¶Ð¸Ð²Ð°ÐµÑ Ð¿Ð¾Ð´Ð³Ð¾ÑÐ¾Ð²ÐºÑ Ð¸Ð·Ð¾Ð±ÑажениÑ'); 1434 ctx.imageSmoothingEnabled = true; 1435 ctx.imageSmoothingQuality = 'high'; 1436 ctx.drawImage(image, sx, sy, sw, sh, 0, 0, width, height); 1437 1438 var blob = await outputPresetBlob(canvas, preset); 1439 canvas.width = canvas.height = 1; 1440 var objectUrl = URL.createObjectURL(blob); 1441 var link = document.createElement('a'); 1442 link.href = objectUrl; 1443 link.download = preset.fileName || 'prepared-image.jpg'; 1444 document.body.appendChild(link); 1445 link.click(); 1446 downloadState.started(); 1447 link.remove(); 1448 setTimeout(function() { URL.revokeObjectURL(objectUrl); }, 1000); 1449 } catch (e) { 1450 downloadState.reset(); 1451 alert(e && e.message ? e.message : 'Ðе ÑдалоÑÑ Ð¿Ð¾Ð´Ð³Ð¾ÑовиÑÑ Ð¸Ð·Ð¾Ð±Ñажение. ÐопÑобÑйÑе еÑÑ Ñаз.'); 1452 } 1453} 1454 1455 1456function setStatus(type, html) { 1457 var bar = document.getElementById('statusBar'); 1458 var dot = document.getElementById('statusDot'); 1459 var text = document.getElementById('statusText'); 1460 var title = document.getElementById('statusTitle'); 1461 var hadProgress = !!imageProgressStartedAt; 1462 bar.style.display = 'flex'; 1463 dot.className = 'status-dot ' + type; 1464 text.innerHTML = html; 1465 bar.className = 'status-bar is-' + type + ((!hadProgress || type === 'fail') ? ' is-simple' : ''); 1466 1467 if (type === 'ok') { 1468 finishImageProgress(true); 1469 if (title) title.textContent = 'ÐзобÑажение гоÑово'; 1470 } else if (type === 'fail') { 1471 finishImageProgress(false); 1472 if (title) title.textContent = 'Ðе ÑдалоÑÑ ÑоздаÑÑ Ð¸Ð·Ð¾Ð±Ñажение'; 1473 } else if (title) { 1474 title.textContent = 'СоздаÑм изобÑажение'; 1475 } 1476 1477 if (type === 'running' && hadProgress && window.innerWidth <= 700 && !bar.hasAttribute('data-progress-scrolled')) { 1478 bar.setAttribute('data-progress-scrolled', '1'); 1479 setTimeout(function(){ bar.scrollIntoView({behavior:'smooth', block:'center'}); }, 80); 1480 } 1481 1482 if (type !== 'running') { 1483 imageProgressStartedAt = 0; 1484 imageProgressServerState = 'starting'; 1485 } 1486} 1487 1488var imageThumbUpSvg = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"/></svg>'; 1489var imageThumbDownSvg = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"/></svg>'; 1490 1491function imageRatingHtml(taskId, rating) { 1492 if (!taskId) return ''; 1493 return '<div class="unitRating imageRating" role="group" aria-label="ÐÑениÑÑ Ð¸Ð·Ð¾Ð±Ñажение" data-taskid="' + escAttr(taskId) + '">' 1494 + '<span class="unitRatingVote unitRatingVoteUp' + (rating === 1 ? ' is-active' : '') + '">' 1495 + '<a href="#" onclick="rateImage(this,1);return false;" class="likeA unitRatingAction" title="ÐÑавиÑÑÑ" aria-label="ÐÑавиÑÑÑ">' + imageThumbUpSvg + '</a></span>' 1496 + '<span class="unitRatingDivider" aria-hidden="true"></span>' 1497 + '<span class="unitRatingVote unitRatingVoteDown' + (rating === -1 ? ' is-active' : '') + '">' 1498 + '<a href="#" onclick="rateImage(this,-1);return false;" class="likeA unitRatingAction" title="Ðе нÑавиÑÑÑ" aria-label="Ðе нÑавиÑÑÑ">' + imageThumbDownSvg + '</a></span></div>'; 1499} 1500 1501function updateImageRating(group, rating) { 1502 var taskId = group.dataset.taskid; 1503 document.querySelectorAll('.imageRating').forEach(function(item){ 1504 if (item.dataset.taskid !== taskId) return; 1505 item.querySelector('.unitRatingVoteUp').classList.toggle('is-active', rating === 1); 1506 item.querySelector('.unitRatingVoteDown').classList.toggle('is-active', rating === -1);
1507 item.dataset.rating = String(rating); 1508 }); 1509} 1510 1511function loadImageRating(group) { 1512 var taskId = group && group.dataset.taskid; 1513 if (!taskId) return; 1514 fetch(SELF + '&action=rating&taskId=' + encodeURIComponent(taskId)) 1515 .then(function(r){ return r.json(); }) 1516 .then(function(json){ if (json.ok) updateImageRating(group, Number(json.rating) || 0); }) 1517 .catch(function(){}); 1518} 1519 1520function rateImage(link, vote) { 1521 var group = link.closest('.imageRating'); 1522 if (!group || group.dataset.loading === '1') return; 1523 group.dataset.loading = '1'; 1524 var fd = new FormData(); 1525 fd.append('taskId', group.dataset.taskid || ''); 1526 fd.append('vote', String(vote)); 1527 fetch(SELF + '&action=rating', {method:'POST', body:fd}) 1528 .then(function(r){ return r.json(); }) 1529 .then(function(json){ if (json.ok) updateImageRating(group, Number(json.rating) || 0); }) 1530 .finally(function(){ group.dataset.loading = '0'; }); 1531} 1532 1533function showResult(url, fmt, ts, isRestored, taskId) { 1534 var dateStr = ''; 1535 if (ts) { 1536 var d = new Date(ts); 1537 dateStr = d.toLocaleDateString('ru-RU') + ' в ' + d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); 1538 } 1539 var savedHtml = (isRestored && dateStr) ? '<div class="result-meta">🕐 Ð¡Ð¾Ñ Ñанено ' + dateStr + '</div>' : ''; 1540 var metaHtml = (savedHtml || taskId) ? '<div class="result-meta-row">' + savedHtml + imageRatingHtml(taskId, 0) + '</div>' : ''; 1541 var rawUrl = url; 1542 url = url.replace('storage.yandexcloud.net', 'hostname4'); 1543 url = url.replace('tempfile.aiquickdraw.com', 'hostname'); 1544 document.getElementById('result').innerHTML = 1545 '<div class="result-img-wrap">' 1546 + '<a href="' + sugUrl(url) + '" target="_blank" onclick="return sugProxyNavigate(event,this)">' 1547 + '<img src="' + sugUrl(url) + '" onerror="sugProxyImageFallback(this)" alt="РезÑлÑÑÐ°Ñ Ð³ÐµÐ½ÐµÑаÑии">' 1548 + '</a>' 1549 + '</div>' 1550 + metaHtml 1551 + '<div class="result-actions">' 1552 + imageOutputPresetButton(rawUrl, 'btn-secondary') 1553 + '<a class="btn-secondary image-download-control" href="' + sugDow(url) + '" download onclick="return imageDownloadClick(event,this)">📥 СкаÑаÑÑ ' + fmt.toUpperCase() + '</a>' 1554 + '<a class="btn-secondary" href="' + sugUrl(url) + '" target="_blank" onclick="return sugProxyNavigate(event,this)">🖼️ ÐÑкÑÑÑÑ</a>' 1555 + '<a class="btn-secondary firstGen" href="/price" onclick="return handleWatermarkClick(event,this)">УбÑаÑÑ Ð²Ð¾Ð´Ñной знак</a>' 1556+ '<a class="btn-secondary" style="cursor:pointer" data-kieurl="' + escAttr(rawUrl) + '" onclick="refineImage(this.dataset.kieurl)">✎️ ÐоÑабоÑаÑÑ</a>' 1557+ '<button class="btn-clear" onclick="clearResult()">✕ УдалиÑÑ</button>' 1558 + '</div>'; 1559 var currentRating = document.querySelector('#result .imageRating'); 1560 if (currentRating) loadImageRating(currentRating); 1561} 1562 1563/* ââââââââââââââââââââââââââââââââ 1564 ÐÑÑоÑÐ¸Ñ Ð³ÐµÐ½ÐµÑаÑий 1565ââââââââââââââââââââââââââââââââ */ 1566var historyOffset = 0; 1567var historyLoading = false; 1568var historyTotal = 0; 1569var HISTORY_LIMIT = 20; 1570 1571function loadHistory(append) { 1572 if (historyLoading) return; 1573 historyLoading = true; 1574 1575 if (!append) { 1576 historyOffset = 0; 1577 document.getElementById('historyGrid').innerHTML = ''; 1578 } 1579 1580 fetch(SELF + '&action=history&offset=' + historyOffset) 1581 .then(function(r){ return r.json(); }) 1582 .then(function(json){ 1583 historyLoading = false; 1584 var items = json.items || []; 1585 1586 if (!append && items.length === 0) { 1587 document.getElementById('historySection').style.display = 'none'; 1588 return; 1589 } 1590 1591 document.getElementById('historySection').style.display = 'block'; 1592 historyOffset += items.length; 1593 1594 var grid = document.getElementById('historyGrid'); 1595 items.forEach(function(item){ 1596 grid.insertAdjacentHTML('beforeend', renderHistCard(item)); 1597 }); 1598 1599 // ÐапÑÑкаем опÑÐ¾Ñ Ð·Ð°Ð´Ð°Ñ, коÑоÑÑе еÑÑ Ð² обÑабоÑке 1600 pollStaleTasksFromHistory(items); 1601 1602 /* Ðнопка «еÑÑ»: показÑваем еÑли веÑнÑлоÑÑ Ñовно HISTORY_LIMIT */ 1603 document.getElementById('historyMore').style.display = 1604 (items.length >= HISTORY_LIMIT) ? 'block' : 'none'; 1605 1606 /* СÑÑÑÑик */ 1607 historyTotal += items.length; 1608 document.getElementById('historyCount').textContent = 1609 historyTotal + '\u00a0запиÑей'; 1610 }) 1611 .catch(function(){ historyLoading = false; }); 1612} 1613 1614function renderHistCard(item) { 1615 var rawResultUrl = item.result_url || ''; 1616 item.result_url = rawResultUrl 1617 .replace('tempfile.aiquickdraw.com', 'hostname') 1618 .replace('storage.yandexcloud.net', 'hostname4'); 1619 var proxy = sugUrl(item.result_url || ''); 1620 var dow = sugDow(item.result_url || ''); 1621 1622 /* ÐиниаÑÑÑа */ 1623 var thumbHtml = item.result_url 1624 ? '<img class="hist-thumb" src="' + proxy + '" onerror="sugProxyImageFallback(this)" alt="" loading="lazy">' 1625 : '<div class="hist-thumb-placeholder">🖼️</div>'; 1626 1627 /* ÐÑÐ¾Ð¼Ð¿Ñ */ 1628 var promptHtml = item.prompt 1629 ? '<div class="hist-prompt" title="ÐажмиÑе, ÑÑÐ¾Ð±Ñ ÑазвеÑнÑÑÑ/ÑвеÑнÑÑÑ" onclick="this.classList.toggle(\'expanded\')">' + escHtml(item.prompt) + '</div>' 1630 : ''; 1631 1632 /* Ðейджи меÑаданнÑÑ */ 1633 var badges = ''; 1634 if (item.resolution) badges += '<span class="hist-badge">' + escHtml(item.resolution) + '</span>'; 1635 if (item.format) badges += '<span class="hist-badge">' + escHtml(item.format.toUpperCase()) + '</span>'; 1636 if (item.ratio && item.ratio !== 'auto') 1637 badges += '<span class="hist-badge">' + escHtml(item.ratio) + '</span>'; 1638 if (item.is_free) badges += '<span class="hist-badge free">ÐеÑплаÑно</span>'; 1639 else if (item.pro_cost > 0) badges += '<span class="hist-badge">' + item.pro_cost + ' PRO</span>'; 1640 1641 if (item.status === 'pending' || item.status === 'processing') 1642 badges += '<span class="hist-badge pending">⏳ Ð обÑабоÑке</span>'; 1643 else if (item.status === 'failed') 1644 badges += '<span class="hist-badge failed">❌ ÐÑибка</span>'; 1645 1646 /* ÐаÑа */ 1647 var dateStr = ''; 1648 if (item.created_at) { 1649 var d = new Date(item.created_at.replace(' ', 'T') + 'Z'); 1650 dateStr = d.toLocaleDateString('ru-RU') + ' ' + 1651 d.toLocaleTimeString('ru-RU', {hour:'2-digit',minute:'2-digit'}); 1652 badges += '<span class="hist-badge"' + (item.result_url ? '' : ' style="margin-left:auto;"') + '>
1652' + dateStr + '</span>'; 1653 } 1654 if (item.result_url) badges += imageRatingHtml(item.taskId, Number(item.rating) || 0); 1655 1656 /* Ðнопки дейÑÑвий */ 1657 var actionsHtml = '<div class="hist-actions">'; 1658if (item.result_url) { 1659 actionsHtml += '<a class="hist-btn" href="' + proxy + '" target="_blank" onclick="return sugProxyNavigate(event,this)">🖼️ ÐÑкÑÑÑÑ</a>'; 1660 actionsHtml += '<a class="hist-btn image-download-control" href="' + dow + '" download onclick="return imageDownloadClick(event,this)">📥 Ð¡ÐºÐ°ÑаÑÑ</a>'; 1661 actionsHtml += imageOutputPresetButton(rawResultUrl, 'hist-btn'); 1662 if (!isPaidUser) { 1663 actionsHtml += '<a class="hist-btn firstGen" href="/price" onclick="return handleWatermarkClick(event,this)">УбÑаÑÑ Ð²Ð¾Ð´Ñной знак</a>'; 1664 } 1665 actionsHtml += '<button class="hist-btn" data-kieurl="' + escAttr(rawResultUrl) + '" onclick="refineImage(this.dataset.kieurl)">✎️ ÐоÑабоÑаÑÑ</button>'; 1666} 1667 if (item.prompt) { 1668 actionsHtml += '<button class="hist-btn" data-prompt="' + 1669 escAttr(encodeURIComponent(item.prompt)) + 1670 '" onclick="copyHistPrompt(this)">📋 ÐопиÑоваÑÑ Ð¾Ð¿Ð¸Ñание</button>'; 1671 } 1672 actionsHtml += '<button class="hist-btn danger" onclick="hideHistItem(this,\'' + 1673 escAttr(item.taskId) + '\')">✕ Ð£Ð´Ð°Ð»Ð¸ÑÑ</button>'; 1674 actionsHtml += '</div>'; 1675 1676 return '<div class="hist-card" id="hc_' + escAttr(item.taskId) + '">' 1677 + thumbHtml 1678 + '<div class="hist-body">' 1679 + promptHtml 1680 + '<div class="hist-meta">' + badges + '</div>' 1681 + '</div>' 1682 + actionsHtml 1683 + '</div>'; 1684} 1685 1686function hideHistItem(btn, taskId) { 1687 btn.disabled = true; 1688 var fd = new FormData(); 1689 fd.append('taskId', taskId); 1690 fetch(SELF + '&action=hide&from=sug.php', { method: 'POST', body: fd }) 1691 .then(function(r){ return r.json(); }) 1692 .then(function(json){ 1693 if (json.ok) { 1694 var card = document.getElementById('hc_' + taskId); 1695 if (card) { 1696 card.style.transition = 'opacity .3s,transform .3s'; 1697 card.style.opacity = '0'; 1698 card.style.transform = 'scale(.95)'; 1699 setTimeout(function(){ card.remove(); }, 300); 1700 } 1701 } else { 1702 btn.disabled = false; 1703 } 1704 }) 1705 .catch(function(){ btn.disabled = false; }); 1706} 1707 1708/* ÐопиÑование опиÑÐ°Ð½Ð¸Ñ (пÑомпÑа) из каÑÑоÑки иÑÑоÑии в бÑÑÐµÑ Ð¾Ð±Ð¼ÐµÐ½Ð° */ 1709function copyHistPrompt(btn) { 1710 var text = ''; 1711 try { text = decodeURIComponent(btn.getAttribute('data-prompt') || ''); } 1712 catch (e) { text = btn.getAttribute('data-prompt') || ''; } 1713 if (!text) return; 1714 1715 var done = function () { 1716 var orig = btn.innerHTML; 1717 if (btn._copyTimer) clearTimeout(btn._copyTimer); 1718 btn.innerHTML = '✔ Ð¡ÐºÐ¾Ð¿Ð¸Ñовано'; 1719 btn._copyTimer = setTimeout(function () { btn.innerHTML = orig; }, 1500); 1720 }; 1721 1722 var fallback = function () { 1723 var ta = document.createElement('textarea'); 1724 ta.value = text; 1725 ta.setAttribute('readonly', ''); 1726 ta.style.position = 'fixed'; 1727 ta.style.top = '-1000px'; 1728 document.body.appendChild(ta); 1729 ta.select(); 1730 try { document.execCommand('copy'); done(); } 1731 catch (e) {} 1732 document.body.removeChild(ta); 1733 }; 1734 1735 if (navigator.clipboard && navigator.clipboard.writeText) { 1736 navigator.clipboard.writeText(text).then(done, fallback); 1737 } else { 1738 fallback(); 1739 } 1740} 1741 1742function escHtml(s) { 1743 return String(s) 1744 .replace(/&/g,'&') 1745 .replace(/</g,'<') 1746 .replace(/>/g,'>') 1747 .replace(/"/g,'"'); 1748} 1749function escAttr(s) { return escHtml(s); } 1750 1751/* ââââââââââââââââââââââââââââââââ 1752 ÐпÑÐ¾Ñ Ð·Ð°Ð²Ð¸ÑÑÐ¸Ñ Ð·Ð°Ð´Ð°Ñ Ð¸Ð· иÑÑоÑии 1753ââââââââââââââââââââââââââââââââ */ 1754var _stalePollTimers = {}; // taskId â intervalId 1755 1756function pollStaleTask(taskId, format) { 1757 if (_stalePollTimers[taskId]) return; // Ñже опÑаÑиваем 1758 1759 _stalePollTimers[taskId] = setInterval(function() { 1760 requestImageStatus(taskId) 1761 .then(function(data) { 1762 1763 if (data.state === 'success' && data.imageUrl) { 1764 clearInterval(_stalePollTimers[taskId]); 1765 delete _stalePollTimers[taskId]; 1766 // ÐеÑезагÑÑжаем иÑÑоÑÐ¸Ñ Ñеликом â пÑоÑе и надÑжнее 1767 setTimeout(function() { loadHistory(false); }, 600); 1768 1769 } else if (data.state === 'retrying' && data.retryTaskId) { 1770 // ÐеÑеклÑÑаемÑÑ Ð½Ð° новÑй taskId 1771 clearInterval(_stalePollTimers[taskId]); 1772 delete _stalePollTimers[taskId]; 1773 pollStaleTask(data.retryTaskId, format); 1774 1775 } else if (data.state === 'failed' 1776 || data.state === 'fail' 1777 || data.state === 'error' 1778 || data.state === 'cancelled') { 1779 clearInterval(_stalePollTimers[taskId]); 1780 delete _stalePollTimers[taskId]; 1781 // ÐбновлÑем ÑолÑко бейдж в каÑÑоÑке, не пеÑегÑÑÐ¶Ð°Ñ Ð²ÑÑ Ð¸ÑÑоÑÐ¸Ñ 1782 var card = document.getElementById('hc_' + taskId); 1783 if (card) { 1784 var badge = card.querySelector('.hist-badge.pending'); 1785 if (badge) { 1786 badge.className = 'hist-badge failed'; 1787 badge.innerHTML = '❌ ÐÑибка'; 1788 } 1789 } 1790 } 1791 // инаÑе â waiting/queuing/generating â пÑодолжаем опÑÐ¾Ñ 1792 }) 1793 .catch(function() { /* ÑеÑÑ â пÑобÑем Ñнова в ÑледÑÑÑем инÑеÑвале */ }); 1794 }, POLL_MS); 1795} 1796 1797function pollStaleTasksFromHistory(items) { 1798 var staleStatuses = { pending: 1, processing: 1, retrying: 1 };
1799 items.forEach(function(item) { 1800 if (staleStatuses[item.status]) { 1801 pollStaleTask(item.taskId, item.format || 'jpg'); 1802 } 1803 }); 1804} 1805 1806/* ÐагÑÑжаем иÑÑоÑÐ¸Ñ Ð¿Ñи ÑÑаÑÑе */ 1807puReady.then(function(){ loadHistory(false); }); 1808 1809/* ÐбновлÑем иÑÑоÑÐ¸Ñ Ð¿Ð¾Ñле ÑÑпеÑной генеÑаÑии */ 1810var _origNotify = notifyImageReadyOnce; 1811notifyImageReadyOnce = function() { 1812 _origNotify(); 1813 setTimeout(function(){ loadHistory(false); }, 1500); 1814}; 1815document.getElementById('fastModel').addEventListener('change', function() { 1816 if (this.checked) { 1817 document.getElementById('nsmode').checked = false; 1818 document.getElementById('textQuality').checked = false; 1819 // РазблокиÑÑем ÑазÑеÑение еÑли textQuality его блокиÑовал 1820 Array.from(document.getElementById('resolution').options) 1821 .forEach(function(o) { o.disabled = false; }); 1822 if (uploadedImages.length > 9) { 1823 uploadedImages = uploadedImages.slice(0, 9); 1824 renderPreviews(); 1825 } 1826 } 1827 // ÐбновлÑем hint вÑегда â и пÑи вклÑÑении, и пÑи вÑклÑÑении 1828 updateUploadMaxHint(); 1829 updateNsUploadWarn(); 1830 applyModelConstraints(); 1831 saveForm(); 1832}); 1833document.getElementById('nsmode').addEventListener('change', function() { 1834 if (this.checked) { 1835 if (document.getElementById('goReg')) { 1836 this.checked = false; 1837 var hint = document.getElementById('nsModeHint'); 1838 if (!hint) { 1839 hint = document.createElement('div'); 1840 hint.id = 'nsModeHint'; 1841 hint.style.cssText = 'margin-top:6px;font-size:13px;color:#c0392b;'; 1842 hint.innerHTML = 'ÐÐ»Ñ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑÑой модели Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¾ <a href="https://sinonim.org/reg?image">заÑегиÑÑÑиÑоваÑÑÑÑ</a>. ÐведÑннÑе даннÑе ÑÐ¾Ñ ÑанÑÑÑÑ.'; 1843 this.closest('label').insertAdjacentElement('afterend', hint); 1844 } 1845 hint.style.display = 'block'; 1846 return; 1847 } 1848 document.getElementById('fastModel').checked = false; 1849 document.getElementById('textQuality').checked = false; 1850 // РазблокиÑÑем ÑазÑеÑение еÑли textQuality его блокиÑовал 1851 Array.from(document.getElementById('resolution').options) 1852 .forEach(function(o) { o.disabled = false; }); 1853 } 1854 var hint = document.getElementById('nsModeHint'); 1855 if (hint) hint.style.display = 'none'; 1856 // ÐлокиÑÑем ÑазÑеÑение вÑÑе 1K 1857 var resEl = document.getElementById('resolution'); 1858 if (this.checked) { 1859 resEl.value = '1K'; 1860 Array.from(resEl.options).forEach(function(o) { 1861 o.disabled = o.value !== '1K'; 1862 }); 1863 updateBtnCost(); 1864 } else { 1865 Array.from(resEl.options).forEach(function(o) { o.disabled = false; }); 1866 } 1867 1868 // ÐÐ¸Ð¼Ð¸Ñ Ð·Ð°Ð³ÑÑзок и Ñ Ð¸Ð½Ñ 1869 var maxImg = getMaxImages(); 1870 if (uploadedImages.length > maxImg) { 1871 uploadedImages = uploadedImages.slice(0, maxImg); 1872 renderPreviews(); 1873 saveUploads(); 1874 } 1875 updateUploadMaxHint(); 1876 1877 updateNsUploadWarn(); 1878 1879 applyModelConstraints(); 1880 saveForm(); 1881});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.