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>
This commit is contained in:
Maxim Dolgolyov
2026-06-12 10:41:59 +03:00
parent db2fccef56
commit d6faf6b22c
8 changed files with 206 additions and 5 deletions
+21 -1
View File
@@ -560,6 +560,7 @@
<script src="https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js"></script>
<script src="/js/api.js"></script>
<script src="/js/imggen.js"></script>
<script src="/js/sidebar.js"></script>
<script src="/js/notifications.js"></script>
<script src="/js/search.js"></script>
@@ -951,13 +952,32 @@ function imgRowHtml(c, side) {
</button>
</div></div>`;
}
return `<div class="card-img-row">
return `<div class="card-img-row" style="display:flex;gap:6px;flex-wrap:wrap">
<button class="card-img-add" onclick="pickCardImage(${c.id},'${side}')">
<svg class="ic" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
Картинка
</button>
<button class="card-img-add" onclick="genCardImage(${c.id},'${side}')" title="Сгенерировать с ИИ">
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 3l2.2 6.3L22 12l-6.8 2.7L13 21l-2.2-6.3L4 12l6.8-2.7z"/><path d="M5 4v3M3.5 5.5h3"/></svg>
ИИ
</button></div>`;
}
function genCardImage(cardId, side) {
if (!LS.imagePromptModal) { LS.toast('Модуль генерации не загружен'); return; }
const card = _cards.find(c => c.id === cardId);
LS.imagePromptModal({
title: 'Картинка для карточки',
placeholder: card && card[side === 'front' ? 'front' : 'back'] ? 'Иллюстрация к: ' + (card[side === 'front' ? 'front' : 'back'] || '') : '',
onUse: async function (url) {
const c = _cards.find(x => x.id === cardId); if (!c) return;
const field = side === 'front' ? 'front_image' : 'back_image';
await LS.api(`/api/flashcards/cards/${cardId}`, { method: 'PUT', body: JSON.stringify({ [field]: url }) }).catch(()=>{});
c[field] = url; updateCardImgRow(cardId, side); LS.toast('Картинка добавлена', 'success');
}
});
}
async function uploadFcImage(file) {
if (!file || !file.type || !file.type.startsWith('image/')) throw new Error('Только изображения');
if (file.size > 5 * 1024 * 1024) throw new Error('Файл больше 5 МБ');