Настройки FLUX
Source file: tgmenu/settings/flux3.html
Live URL: https://tgmenu.pages.dev/settings/flux3.html
Code¶
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Настройки FLUX</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
:root {
--bg: var(--tg-theme-bg-color, #0b0d10);
--text: var(--tg-theme-text-color, #e6e6e6);
--hint: var(--tg-theme-hint-color, #9aa3ab);
--button: var(--tg-theme-button-color, #7c6cff);
--button-text: var(--tg-theme-button-text-color, #fff);
--sec-bg: var(--tg-theme-secondary-bg-color, #151821);
--field-bg: #0f1216;
--border-subtle: rgba(255,255,255,.12);
--border-strong: rgba(255,255,255,.24);
}
html, body { height: 100%; }
body {
margin: 0;
padding: 16px 16px 84px;
font: 15px/1.45 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, "Helvetica Neue", Arial;
background: var(--bg);
color: var(--text);
}
h1 {
margin: 0 0 16px;
font-size: 20px;
}
form {
display: block;
}
fieldset {
border: 1px solid var(--border-subtle);
border-radius: 16px;
padding: 14px 14px 10px;
margin: 0 0 12px;
background: var(--sec-bg);
}
legend {
padding: 0 6px;
color: var(--hint);
font-size: 12px;
}
label {
display: block;
margin: 10px 0 6px;
font-size: 13px;
font-weight: 600;
}
select,
input[type="number"] {
width: 100%;
box-sizing: border-box;
background: var(--field-bg);
color: var(--text);
border: 1px solid var(--border-subtle);
border-radius: 999px;
padding: 10px 14px;
outline: none;
font: inherit;
appearance: none;
}
select:focus,
input[type="number"]:focus {
border-color: var(--border-strong);
}
.row {
display: flex;
gap: 10px;
align-items: center;
}
.row-inline {
margin-top: 4px;
}
.row-inline input[type="number"] {
flex: 1;
}
.switch {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--hint);
cursor: pointer;
user-select: none;
padding: 4px 8px;
border-radius: 999px;
border: 1px solid var(--border-subtle);
background: rgba(0,0,0,.12);
white-space: nowrap;
}
.switch input {
accent-color: var(--button);
}
.ref-row {
margin-top: 4px;
display: flex;
gap: 10px;
align-items: center;
}
.ref-col {
flex: 1;
}
input[type="range"] {
width: 100%;
}
.ref-value {
margin-top: 4px;
font-size: 12px;
color: var(--hint);
}
.ref-disabled {
opacity: 0.4;
}
.bar {
position: fixed;
inset: auto 0 0 0;
padding: 12px 16px 18px;
background: linear-gradient(
180deg,
rgba(0,0,0,0) 0,
rgba(0,0,0,.25) 22%,
var(--bg) 50%
);
display: grid;
gap: 10px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 14px;
border-radius: 12px;
border: 1px solid var(--border-subtle);
background: var(--sec-bg);
color: var(--text);
font-weight: 600;
font-size: 15px;
cursor: pointer;
}
.btn.primary {
background: var(--button);
color: var(--button-text);
border-color: transparent;
}
.btn.ghost {
background: transparent;
}
#status {
font-size: 12px;
color: var(--hint);
min-height: 16px;
}
</style>
</head>
<body>
<h1>Настройки FLUX</h1>
<form id="form" autocomplete="off">
<fieldset>
<legend>Основные параметры</legend>
<!-- Модель -->
<label for="model">Модель</label>
<select id="model">
<option value="flux-krea" selected>Flux-Krea</option>
</select>
<!-- Качество -->
<label for="qualityMode">Качество</label>
<select id="qualityMode">
<!-- Light временно скрыт -->
<option value="standard" selected>Standard</option>
<option value="pro">Pro</option>
</select>
<!-- AR -->
<label for="ar">Соотношение сторон</label>
<select id="ar">
<!-- сверху вертикальные -->
<option value="3:4">3:4 · вертикальное</option>
<option value="2:3">2:3 · вертикальное</option>
<option value="9:16">9:16 · вертикальное</option>
<!-- центр -->
<option value="1:1" selected>1:1 · квадрат</option>
<!-- снизу горизонтальные -->
<option value="4:3">4:3 · горизонтальное</option>
<option value="3:2">3:2 · горизонтальное</option>
<option value="16:9">16:9 · горизонтальное</option>
<option value="21:9">21:9 · горизонтальное</option>
</select>
<!-- Batch / Генераций за раз -->
<label for="batch">Генераций за раз</label>
<select id="batch">
<option value="1" selected>1</option>
<!-- заготовка на будущее:
<option value="2">2</option>
<option value="4">4</option>
-->
</select>
<!-- Seed -->
<label for="seed">Seed</label>
<div class="row row-inline">
<input id="seed" type="number" min="0" max="999999" inputmode="numeric" />
<label class="switch" for="random">
<input id="random" type="checkbox" />
<span>random</span>
</label>
</div>
<!-- REF -->
<label>Сила референса</label>
<div class="ref-row">
<div class="ref-col" id="refBlock">
<input id="ref" type="range" min="0" max="120" step="1" />
<div class="ref-value">
Сила: <span id="refValue">75</span>%
</div>
</div>
<label class="switch" for="useRef">
<input id="useRef" type="checkbox" />
<span>Использовать реф</span>
</label>
</div>
</fieldset>
</form>
<div class="bar">
<button type="button" id="reset" class="btn ghost">Сбросить</button>
<button type="button" id="save" class="btn primary">Сохранить</button>
<div id="status" aria-live="polite"></div>
</div>
<script>
// --- Telegram / CloudStorage setup ---
const tg = window.Telegram?.WebApp;
if (tg) {
tg.ready();
tg.expand();
if (tg.MainButton) {
tg.MainButton.hide();
tg.MainButton.offClick?.(() => {});
}
}
const CS = tg?.CloudStorage || null;
const STORAGE_KEY = 'settings:flux:v1';
const DEFAULTS = {
model: 'flux-krea',
// quality: 0=light, 1=standard, 2=pro
quality: 1,
qualityMode: 'standard',
ar: '1:1',
batch: 1,
ref: 0.75, // 75%
seed: 0, // дефолт 0
random: false,
useRef: false,
};
const REF_MAX_PCT = 120;
const REF_DEFAULT_PCT = Math.round(DEFAULTS.ref * 100);
const $ = (s) => document.querySelector(s);
const $model = $('#model');
const $qualityMode = $('#qualityMode');
const $ar = $('#ar');
const $batch = $('#batch');
const $seed = $('#seed');
const $random = $('#random');
const $useRef = $('#useRef');
const $ref = $('#ref');
const $refValue = $('#refValue');
const $refBlock = $('#refBlock');
const $save = $('#save');
const $reset = $('#reset');
const $status = $('#status');
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
const parseNum = (v, def) => {
const n = Number(v);
return Number.isFinite(n) ? n : def;
};
function qualityToMode(q) {
if (q === 0) return 'light';
if (q === 2) return 'pro';
return 'standard';
}
function modeToQuality(mode) {
if (mode === 'light') return 0;
if (mode === 'pro') return 2;
return 1;
}
function parseCfg(raw) {
try {
const cfg = JSON.parse(raw);
// нормализуем quality
let qNum = Number(cfg.quality);
if (!Number.isFinite(qNum)) {
if (cfg.qualityMode && ['light','standard','pro'].includes(cfg.qualityMode)) {
qNum = modeToQuality(cfg.qualityMode);
} else {
qNum = DEFAULTS.quality;
}
}
const qualityMode = cfg.qualityMode || qualityToMode(qNum);
const ar = /^\d+:\d+$/.test(String(cfg.ar || ''))
? String(cfg.ar)
: DEFAULTS.ar;
let refNum = parseNum(cfg.ref, DEFAULTS.ref);
refNum = clamp(refNum, 0, 1.2);
let seed = parseInt(cfg.seed, 10);
if (!Number.isFinite(seed) || seed < 0) seed = DEFAULTS.seed;
if (seed > 999999) seed = seed % 1000000;
const random = !!cfg.random;
const useRef = !!cfg.useRef;
let batch = Number(cfg.batch);
if (!Number.isFinite(batch) || ![1,2,4].includes(batch)) {
batch = DEFAULTS.batch;
}
return {
...DEFAULTS,
...cfg,
quality: qNum,
qualityMode,
ar,
ref: refNum,
seed,
random,
useRef,
batch,
};
} catch {
return { ...DEFAULTS };
}
}
function loadConfig() {
return new Promise((resolve) => {
// нет CloudStorage → только localStorage
if (!CS) {
const raw = localStorage.getItem(STORAGE_KEY);
return resolve(raw ? parseCfg(raw) : { ...DEFAULTS });
}
CS.getItem(STORAGE_KEY, (err, value) => {
if (!err && value) {
try { localStorage.setItem(STORAGE_KEY, value); } catch {}
return resolve(parseCfg(value));
}
// нет в облаке → пробуем localStorage
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const cfg = parseCfg(raw);
const clean = JSON.stringify(cfg);
CS.setItem(STORAGE_KEY, clean, () => resolve(cfg));
} else {
resolve({ ...DEFAULTS });
}
});
});
}
function saveConfig(cfg) {
const merged = {
...DEFAULTS,
...cfg,
};
const clean = JSON.stringify(merged);
try { localStorage.setItem(STORAGE_KEY, clean); } catch {}
if (CS) {
CS.setItem(STORAGE_KEY, clean, (err) => {
if (err) console.warn('CloudStorage setItem error', err);
});
}
}
function applyRandom() {
if ($random.checked) {
$seed.disabled = true;
$seed.value = '';
} else {
$seed.disabled = false;
if ($seed.value === '') {
$seed.value = String(DEFAULTS.seed);
}
}
}
function applyUseRef() {
const on = !!$useRef.checked;
$ref.disabled = !on;
$refBlock.classList.toggle('ref-disabled', !on);
if (!on) {
$refValue.textContent = '0';
} else {
const v = clamp(parseNum($ref.value, REF_DEFAULT_PCT), 0, REF_MAX_PCT);
$refValue.textContent = String(v);
}
}
function gather() {
const model = $model.value || DEFAULTS.model;
const qmRaw = $qualityMode.value || 'standard';
const qualityMode = ['light','standard','pro'].includes(qmRaw)
? qmRaw
: 'standard';
const quality = modeToQuality(qualityMode);
const arIn = String($ar.value || '').replace(/\s+/g, '');
const ar = /^\d+:\d+$/.test(arIn) ? arIn : DEFAULTS.ar;
const batchRaw = parseInt($batch.value, 10);
const batch = [1,2,4].includes(batchRaw) ? batchRaw : 1;
const useRef = !!$useRef.checked;
let refPct = clamp(parseNum($ref.value, REF_DEFAULT_PCT), 0, REF_MAX_PCT);
const ref = useRef ? refPct / 100 : 0;
const random = !!$random.checked;
let seed = 0;
if (!random) {
const cleaned = String($seed.value || '').replace(/[^\d]/g, '');
seed = parseInt(cleaned || '0', 10);
if (!Number.isFinite(seed) || seed < 0) seed = DEFAULTS.seed;
if (seed > 999999) seed = seed % 1000000;
}
return {
model,
qualityMode, // удобно для фронта
quality, // главное числовое поле 0/1/2
ar,
batch,
ref,
seed,
random,
useRef,
};
}
async function saveAll(cfg) {
saveConfig(cfg);
tg?.sendData?.(JSON.stringify({
type: 'flux_settings',
...cfg,
}));
if (tg?.HapticFeedback) {
tg.HapticFeedback.impactOccurred('medium');
}
}
// --- listeners ---
$random.addEventListener('change', applyRandom);
$useRef.addEventListener('change', applyUseRef);
$ref.addEventListener('input', () => {
const v = clamp(parseNum($ref.value, REF_DEFAULT_PCT), 0, REF_MAX_PCT);
$refValue.textContent = String(v);
});
$save.addEventListener('click', async () => {
const cfg = gather();
$status.textContent = 'Сохранение…';
try {
await saveAll(cfg);
$status.textContent = 'Сохранено';
} catch (e) {
console.error(e);
$status.textContent = 'Ошибка сохранения';
}
});
$reset.addEventListener('click', async () => {
const defQM = qualityToMode(DEFAULTS.quality);
$model.value = DEFAULTS.model;
$qualityMode.value = defQM;
$ar.value = DEFAULTS.ar;
$batch.value = String(DEFAULTS.batch);
const refPct = REF_DEFAULT_PCT;
$ref.value = refPct;
$useRef.checked = DEFAULTS.useRef;
$refValue.textContent = DEFAULTS.useRef ? String(refPct) : '0';
$random.checked = DEFAULTS.random;
$seed.value = String(DEFAULTS.seed);
$seed.disabled = false;
applyRandom();
applyUseRef();
const cfg = {
...DEFAULTS,
qualityMode: defQM,
};
$status.textContent = 'Сброс…';
try {
await saveAll(cfg);
$status.textContent = 'Сброшено к дефолтам';
} catch (e) {
console.error(e);
$status.textContent = 'Ошибка сброса';
}
});
document.getElementById('form')
.addEventListener('submit', (e) => e.preventDefault());
// --- init ---
(async () => {
const cfg = await loadConfig();
$model.value = cfg.model || DEFAULTS.model;
const qm = cfg.qualityMode || qualityToMode(cfg.quality);
$qualityMode.value = ['light','standard','pro'].includes(qm)
? qm
: 'standard';
$ar.value = cfg.ar || DEFAULTS.ar;
$batch.value = String(cfg.batch ?? DEFAULTS.batch);
const refPct = clamp(Math.round(cfg.ref * 100), 0, REF_MAX_PCT);
$ref.value = refPct;
$useRef.checked = !!cfg.useRef;
$refValue.textContent = cfg.useRef ? String(refPct) : '0';
const random = !!cfg.random || cfg.seed === 0;
$random.checked = random;
if (!random) {
$seed.value = String(cfg.seed ?? DEFAULTS.seed);
} else {
$seed.value = '';
}
applyRandom();
applyUseRef();
})();
</script>
</body>
</html>