feat(admin): раздел «Генерация картинок» — управление провайдером и тест
Новый админ-раздел: Account ID / токен (маскируется) / модель Cloudflare, лимиты (пауза, дневной лимит) из БД, статистика, кнопка теста генерации. imggenController: лимиты и модель теперь из конфига, поддержка JSON и бинарного ответа CF, переиспользуемые generateImage() и stats(). Бэкенд GET/PUT /api/admin/imggen + POST /api/admin/imggen/test (admin-only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1094,8 +1094,61 @@ async function testAssistant(req, res) {
|
||||
res.json(r);
|
||||
}
|
||||
|
||||
/* ── Генерация картинок (Cloudflare Workers AI) ──────────────────────────── */
|
||||
const IMGGEN_MODELS = [
|
||||
{ id: '@cf/black-forest-labs/flux-1-schnell', label: 'FLUX.1 schnell — рекомендуется, быстрый' },
|
||||
{ id: '@cf/stabilityai/stable-diffusion-xl-base-1.0', label: 'Stable Diffusion XL' },
|
||||
{ id: '@cf/bytedance/stable-diffusion-xl-lightning', label: 'SDXL Lightning — очень быстрый' },
|
||||
{ id: '@cf/lykon/dreamshaper-8-lcm', label: 'DreamShaper 8 LCM' },
|
||||
];
|
||||
function _imgCfg() { try { return JSON.parse(_aset('imggen_provider') || '{}') || {}; } catch (e) { return {}; } }
|
||||
|
||||
function getImggen(_req, res) {
|
||||
const c = _imgCfg();
|
||||
let stats = { count: 0, bytes: 0 };
|
||||
try { stats = require('./imggenController').stats(); } catch (e) {}
|
||||
const cd = Number(c.cooldownMs), dc = Number(c.dailyCap);
|
||||
res.json({
|
||||
provider: c.provider || 'cloudflare',
|
||||
accountId: c.accountId || '',
|
||||
model: c.model || '@cf/black-forest-labs/flux-1-schnell',
|
||||
hasToken: !!c.token,
|
||||
cooldownMs: Number.isFinite(cd) && cd >= 0 ? cd : 4000,
|
||||
dailyCap: Number.isFinite(dc) && dc >= 0 ? dc : 40,
|
||||
enabled: !!(c.provider === 'cloudflare' && c.accountId && c.token),
|
||||
models: IMGGEN_MODELS,
|
||||
stats,
|
||||
});
|
||||
}
|
||||
|
||||
function saveImggen(req, res) {
|
||||
const b = req.body || {};
|
||||
const c = _imgCfg();
|
||||
c.provider = b.provider || c.provider || 'cloudflare';
|
||||
if (b.accountId !== undefined) c.accountId = String(b.accountId || '').trim();
|
||||
if (b.model !== undefined) c.model = String(b.model || '').trim();
|
||||
if (b.clearToken) c.token = '';
|
||||
else if (b.token && b.token !== '••••••••') c.token = String(b.token).trim();
|
||||
if (b.cooldownMs !== undefined) { const n = Number(b.cooldownMs); if (Number.isFinite(n) && n >= 0) c.cooldownMs = n; }
|
||||
if (b.dailyCap !== undefined) { const n = Number(b.dailyCap); if (Number.isFinite(n) && n >= 0) c.dailyCap = n; }
|
||||
db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('imggen_provider', ?)").run(JSON.stringify(c));
|
||||
audit(req, 'imggen.config', 'imggen', 'настройки генерации картинок');
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
async function testImggen(req, res) {
|
||||
const prompt = (String((req.body && req.body.prompt) || '').trim()) || 'a cute friendly mascot, flat illustration, warm tones';
|
||||
try {
|
||||
const out = await require('./imggenController').generateImage(prompt, 0);
|
||||
res.json({ ok: true, url: out.url, prompt: out.prompt });
|
||||
} catch (e) {
|
||||
res.status(502).json({ ok: false, error: e.message || 'Ошибка', detail: e.detail });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getStats, getOverview, globalSearch,
|
||||
getImggen, saveImggen, testImggen,
|
||||
getUsers, updateRole, getUserSessions, getAllSessions, getSessionDetail,
|
||||
clearUserSessions, deleteSession, updateUser, banUser, deleteUser,
|
||||
getFeatures, updateFeatures, getFreeStudentFeatures, updateFreeStudentFeatures,
|
||||
|
||||
Reference in New Issue
Block a user