Files
Learn_System/frontend/js/imggen.js
T
Maxim Dolgolyov d6faf6b22c feat(imggen): генерация картинок ИИ (FLUX.1) — ассистент, флэшкарты, редактор уроков
Бэкенд /api/imggen (status/generate, CF Workers AI, cooldown+дневной лимит).
Переиспользуемый модал LS.imagePromptModal (js/imggen.js).
Квантик: режим «Нарисовать» в чате (inline).
Флэшкарты: кнопка «ИИ» в блоке картинки карточки.
Редактор уроков: кнопка «Сгенерировать» в блоке изображения.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:41:59 +03:00

65 lines
5.4 KiB
JavaScript

'use strict';
/* Переиспользуемый модал генерации картинок. LS.imagePromptModal({title, placeholder, onUse}).
* Зависит от LS.imageGen (api.js) и LS.toast. Подключать на страницах с кнопкой генерации. */
(function () {
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'})[c];}); }
function ensureStyle(){
if (document.getElementById('imggen-style')) return;
var s=document.createElement('style'); s.id='imggen-style';
s.textContent=[
'.ig-ov{position:fixed;inset:0;z-index:2000;display:flex;align-items:center;justify-content:center;background:rgba(15,23,42,.5);backdrop-filter:blur(6px);padding:20px}',
'.ig-box{background:var(--surface,#fff);border:1.5px solid var(--border,#e2e8f0);border-radius:20px;width:440px;max-width:96vw;max-height:92vh;overflow:auto;padding:20px 22px;box-shadow:0 24px 70px rgba(0,0,0,.3)}',
'.ig-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}',
".ig-title{font-family:'Unbounded',sans-serif;font-weight:800;font-size:.98rem}",
'.ig-x{border:none;background:none;font-size:1.4rem;line-height:1;cursor:pointer;color:var(--text-2,#64748b)}',
'.ig-ta{width:100%;box-sizing:border-box;min-height:64px;padding:10px 12px;border:1.5px solid var(--border,#e2e8f0);border-radius:11px;font:inherit;font-size:.86rem;resize:vertical;background:var(--surface,#fff);color:var(--text,#0f172a)}',
'.ig-hint{font-size:.7rem;color:var(--text-3,#94a3b8);margin:6px 0 10px;line-height:1.45}',
".ig-btn{padding:9px 16px;border-radius:10px;border:none;cursor:pointer;font:700 .82rem 'Manrope',sans-serif}",
'.ig-btn.primary{background:var(--violet,#9B5DE5);color:#fff}',
'.ig-btn.ghost{background:transparent;border:1.5px solid var(--border-h,#cbd5e1);color:var(--text-2,#475569)}',
'.ig-btn:disabled{opacity:.55;cursor:not-allowed}',
'.ig-preview{margin-top:14px;border-radius:14px;overflow:hidden;border:1.5px solid var(--border,#e2e8f0);background:#0d0d1f;min-height:120px;display:flex;align-items:center;justify-content:center}',
'.ig-preview img{width:100%;display:block}',
'.ig-busy{color:#9aa5b4;font-size:.84rem;padding:28px;text-align:center}',
'.ig-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}',
].join('');
document.head.appendChild(s);
}
window.LS = window.LS || {};
LS.imagePromptModal = function (opts) {
opts = opts || {}; ensureStyle();
var ov = document.createElement('div'); ov.className = 'ig-ov';
ov.innerHTML = '<div class="ig-box">'
+ '<div class="ig-head"><span class="ig-title">' + esc(opts.title || 'Сгенерировать картинку') + '</span><button class="ig-x" data-x>&times;</button></div>'
+ '<textarea class="ig-ta" placeholder="' + esc(opts.placeholder || 'Опиши картинку: «кот-учёный в очках, плоская иллюстрация»') + '"></textarea>'
+ '<div class="ig-hint">ИИ-картинка для иллюстраций и декора (не для точных схем — графиков, формул). FLUX.1 · бесплатно.</div>'
+ '<div class="ig-actions"><button class="ig-btn primary" data-gen>Сгенерировать</button></div>'
+ '<div class="ig-preview" data-prev style="display:none"></div>'
+ '<div class="ig-actions" data-userow style="display:none"><button class="ig-btn primary" data-use>' + esc(opts.useLabel || 'Использовать') + '</button><button class="ig-btn ghost" data-again>Ещё вариант</button></div>'
+ '</div>';
document.body.appendChild(ov);
var ta = ov.querySelector('.ig-ta'), prev = ov.querySelector('[data-prev]'), useRow = ov.querySelector('[data-userow]'), genBtn = ov.querySelector('[data-gen]');
var lastUrl = null;
if (opts.prompt) ta.value = opts.prompt;
function close(){ ov.remove(); }
ov.addEventListener('click', function (e) { if (e.target === ov || e.target.hasAttribute('data-x')) close(); });
async function gen(){
var prompt = ta.value.trim();
if (prompt.length < 3) { LS.toast && LS.toast('Опиши, что нарисовать', 'warn'); return; }
genBtn.disabled = true; genBtn.textContent = 'Рисую…'; useRow.style.display = 'none';
prev.style.display = 'flex'; prev.innerHTML = '<div class="ig-busy">Генерирую картинку… (5–15 сек)</div>';
try {
var r = await LS.imageGen(prompt);
if (r && r.url) { lastUrl = r.url; prev.innerHTML = '<img src="' + r.url + '" alt="">'; useRow.style.display = 'flex'; genBtn.textContent = 'Перегенерировать'; }
else prev.innerHTML = '<div class="ig-busy">Не получилось</div>';
} catch (e) { prev.innerHTML = '<div class="ig-busy">' + esc((e && e.data && e.data.error) || e.message || 'Ошибка') + '</div>'; }
finally { genBtn.disabled = false; if (genBtn.textContent === 'Рисую…') genBtn.textContent = 'Сгенерировать'; }
}
genBtn.onclick = gen;
ov.querySelector('[data-again]').onclick = gen;
ov.querySelector('[data-use]').onclick = function () { if (lastUrl && opts.onUse) opts.onUse(lastUrl); close(); };
setTimeout(function () { ta.focus(); }, 50);
return ov;
};
})();