Seedream settings
Source file: tgmenu/settings/seedream.html
Live URL: https://tgmenu.pages.dev/settings/seedream.html
Code¶
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Seedream settings</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Telegram WebApp SDK ОБЯЗАТЕЛЕН для CloudStorage + sendData -->
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
:root {
color-scheme: dark;
--bg-gradient-start: #050712;
--bg-gradient-end: #111827;
--card-bg: rgba(15, 23, 42, 0.96);
--card-border: rgba(148, 163, 184, 0.35);
--accent: #8b5cf6;
--accent-soft: rgba(139, 92, 246, 0.16);
--accent-strong: rgba(139, 92, 246, 0.55);
--accent-text: #e5e7eb;
--text-main: #e5e7eb;
--text-muted: #9ca3af;
--chip-bg: rgba(15, 23, 42, 0.9);
--chip-border: rgba(148, 163, 184, 0.4);
--chip-bg-active: rgba(139, 92, 246, 0.22);
--chip-border-active: rgba(167, 139, 250, 0.95);
--danger: #f97373;
}
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text",
"Roboto", "Segoe UI", sans-serif;
background: radial-gradient(circle at top, var(--bg-gradient-start), #020617 45%, var(--bg-gradient-end) 100%);
color: var(--text-main);
}
body {
display: flex;
align-items: stretch;
justify-content: center;
}
.app {
width: 100%;
max-width: 480px;
margin: 0 auto;
padding: 16px 12px 24px;
display: flex;
align-items: stretch;
justify-content: center;
}
.card {
width: 100%;
border-radius: 20px;
padding: 16px 16px 14px;
background: linear-gradient(
145deg,
rgba(15, 23, 42, 0.98),
rgba(15, 23, 42, 0.96)
);
border: 1px solid var(--card-border);
box-shadow:
0 10px 40px rgba(15, 23, 42, 0.9),
0 0 0 1px rgba(15, 23, 42, 0.9);
display: flex;
flex-direction: column;
gap: 14px;
}
.card-header {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 2px;
}
.title-row {
display: flex;
align-items: center;
gap: 10px;
}
.icon-pill {
width: 32px;
height: 32px;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
background: radial-gradient(circle at 30% 0%, #a855f7, #4f46e5 65%, #1e293b 100%);
box-shadow:
0 0 0 1px rgba(129, 140, 248, 0.55),
0 8px 24px rgba(88, 28, 135, 0.65);
font-size: 16px;
}
.card-title {
font-size: 18px;
font-weight: 600;
letter-spacing: 0.01em;
}
.card-subtitle {
font-size: 13px;
color: var(--text-muted);
margin-top: 1px;
}
.card-body {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 4px;
}
.block {
padding: 10px 10px 9px;
border-radius: 14px;
background: radial-gradient(circle at top left, rgba(15,23,42,1), rgba(15,23,42,0.95));
border: 1px solid rgba(148, 163, 184, 0.35);
display: flex;
flex-direction: column;
gap: 8px;
}
.block-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
margin-bottom: 2px;
}
.block-title {
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #9ca3af;
}
.block-caption {
font-size: 12px;
color: var(--text-muted);
opacity: 0.95;
}
.model-row {
display: grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap: 8px;
}
.chip {
font-size: 13px;
padding: 8px 10px;
border-radius: 999px;
border: 1px solid var(--chip-border);
background: var(--chip-bg);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease,
transform 0.08s ease;
user-select: none;
}
.chip span.sub {
font-size: 11px;
color: var(--text-muted);
}
.chip.active {
border-color: var(--chip-border-active);
background: radial-gradient(circle at top left, rgba(79,70,229,0.42), rgba(15,23,42,0.95));
color: var(--accent-text);
box-shadow:
0 0 0 1px rgba(129, 140, 248, 0.7),
0 0 25px rgba(79, 70, 229, 0.6);
}
.chip.active span.sub {
color: rgba(209, 213, 219, 0.85);
}
.chip:hover {
transform: translateY(-0.5px);
}
.segment-row {
display: inline-flex;
border-radius: 999px;
padding: 3px;
background: rgba(15,23,42,0.9);
border: 1px solid rgba(148, 163, 184, 0.5);
}
.segment-btn {
border-radius: 999px;
padding: 5px 11px 6px;
font-size: 13px;
border: none;
outline: none;
background: transparent;
color: var(--text-muted);
cursor: pointer;
min-width: 44px;
text-align: center;
transition: background 0.15s ease, color 0.15s ease, transform 0.08s ease;
}
.segment-btn.active {
background: linear-gradient(135deg, rgba(129, 140, 248, 0.95), rgba(236, 72, 153, 0.9));
color: #f9fafb;
box-shadow:
0 0 0 1px rgba(129, 140, 248, 0.8),
0 0 18px rgba(129, 140, 248, 0.9);
transform: translateY(-0.5px);
}
.segment-btn:not(.active):hover {
background: rgba(148, 163, 184, 0.16);
color: #e5e7eb;
}
.ratio-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0,1fr));
gap: 6px;
}
.ratio-btn {
font-size: 13px;
padding: 7px 4px;
border-radius: 999px;
border: 1px solid var(--chip-border);
background: var(--chip-bg);
color: var(--text-main);
cursor: pointer;
text-align: center;
transition: background 0.15s ease, border-color 0.15s ease,
transform 0.08s ease, color 0.15s ease;
}
.ratio-btn span.sub {
display: block;
margin-top: 1px;
font-size: 11px;
color: var(--text-muted);
}
.ratio-btn.active {
border-color: var(--chip-border-active);
background: radial-gradient(circle at top left, rgba(79,70,229,0.45), rgba(15,23,42,0.96));
color: var(--accent-text);
box-shadow:
0 0 0 1px rgba(129, 140, 248, 0.65),
0 0 20px rgba(79, 70, 229, 0.7);
}
.ratio-btn.active span.sub {
color: rgba(209, 213, 219, 0.9);
}
.ratio-btn:hover {
transform: translateY(-0.5px);
}
.dims-row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 6px;
margin-top: 2px;
}
.dims-main {
font-size: 13px;
color: var(--text-muted);
}
.dims-main strong {
color: #e5e7eb;
font-weight: 600;
}
.dims-extra {
font-size: 11px;
color: var(--text-muted);
opacity: 0.9;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.btn {
border-radius: 999px;
padding: 9px 14px 10px;
border: none;
cursor: pointer;
font-size: 14px;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
flex: 1 1 0;
transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease,
transform 0.08s ease, border-color 0.15s ease;
user-select: none;
white-space: nowrap;
}
.btn-outline {
background: transparent;
color: #e5e7eb;
border: 1px solid rgba(148, 163, 184, 0.65);
}
.btn-primary {
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: #f9fafb;
border: 1px solid rgba(167, 139, 250, 0.9);
box-shadow:
0 10px 25px rgba(88, 28, 135, 0.7),
0 0 0 1px rgba(129, 140, 248, 0.7);
}
.btn-outline:hover {
background: rgba(30, 64, 175, 0.3);
border-color: rgba(129, 140, 248, 0.9);
}
.btn-primary:hover {
box-shadow:
0 14px 30px rgba(88, 28, 135, 0.9),
0 0 0 1px rgba(196, 181, 253, 0.95);
transform: translateY(-1px);
}
.btn:active {
transform: translateY(0px) scale(0.99);
box-shadow: none;
}
.status-row {
margin-top: 4px;
font-size: 11px;
color: var(--text-muted);
display: flex;
justify-content: space-between;
align-items: center;
gap: 4px;
}
.status-pill {
padding: 3px 8px;
border-radius: 999px;
border: 1px solid rgba(148, 163, 184, 0.5);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.09em;
color: #9ca3af;
}
.status-pill.ok {
border-color: rgba(74, 222, 128, 0.8);
color: #bbf7d0;
background: rgba(22, 163, 74, 0.16);
}
.status-pill.err {
border-color: rgba(248, 113, 113, 0.85);
color: #fecaca;
background: rgba(248, 113, 113, 0.18);
}
</style>
</head>
<body>
<div class="app">
<div class="card">
<div class="card-header">
<div class="title-row">
<div class="icon-pill">✨</div>
<div>
<div class="card-title">Настройки Seedream</div>
<!-- Подзаголовок убран по запросу -->
<div class="card-subtitle"></div>
</div>
</div>
</div>
<div class="card-body">
<!-- MODEL -->
<div class="block">
<div class="block-header">
<div class="block-title">Модель</div>
</div>
<div class="model-row">
<button class="chip" data-model="4.5">
<span>Seedream 4.5</span>
</button>
<button class="chip" data-model="4.0">
<span>Seedream 4.0</span>
</button>
</div>
</div>
<!-- QUALITY -->
<div class="block">
<div class="block-header">
<div class="block-title">Качество / разрешение</div>
</div>
<div class="segment-row" id="qualityRow">
<button class="segment-btn" data-quality="1k">1K</button>
<button class="segment-btn" data-quality="2k">2K</button>
<button class="segment-btn" data-quality="4k">4K</button>
</div>
</div>
<!-- RATIO -->
<div class="block">
<div class="block-header">
<div class="block-title">Соотношение сторон</div>
</div>
<div class="ratio-grid" id="ratioGrid">
<button class="ratio-btn" data-ratio="4:3">
<span>4:3</span>
</button>
<button class="ratio-btn" data-ratio="3:4">
<span>3:4</span>
</button>
<button class="ratio-btn" data-ratio="9:16">
<span>9:16</span>
</button>
<button class="ratio-btn" data-ratio="3:2">
<span>3:2</span>
</button>
<button class="ratio-btn" data-ratio="1:1">
<span>1:1</span>
</button>
<button class="ratio-btn" data-ratio="16:9">
<span>16:9</span>
</button>
<button class="ratio-btn" data-ratio="2:3">
<span>2:3</span>
</button>
<button class="ratio-btn" data-ratio="21:9">
<span>21:9</span>
</button>
</div>
<div class="dims-row">
<div class="dims-main" id="dimsMain">
Размер: <strong>—</strong>
</div>
<div class="dims-extra" id="dimsExtra">
Long edge (i2i): —
</div>
</div>
</div>
</div>
<div class="card-footer">
<button class="btn btn-outline" id="resetBtn">Сбросить</button>
<button class="btn btn-primary" id="saveBtn">Сохранить</button>
</div>
<div class="status-row">
<div id="statusText">Локальные настройки</div>
<div id="statusPill" class="status-pill">offline</div>
</div>
</div>
</div>
<script>
(function () {
// Инициализация как во FLUX-настройках
const tg = window.Telegram?.WebApp || null;
if (tg) {
// Сообщаем Телеграму, что WebApp полностью готов
tg.ready?.();
tg.expand();
// Ничего не включаем/не трогаем про closingConfirmation
}
const CS_KEY = "seedream_settings_v2";
const LS_KEY = "seedream_settings_v2";
const RATIO_BASE_2K = {
"1:1": { w: 2048, h: 2048 },
"4:3": { w: 2304, h: 1728 },
"3:4": { w: 1728, h: 2304 },
"16:9": { w: 2560, h: 1440 },
"9:16": { w: 1440, h: 2560 },
"3:2": { w: 2496, h: 1664 },
"2:3": { w: 1664, h: 2496 },
"21:9": { w: 3024, h: 1296 }
};
const LONG_EDGE_BY_QUALITY = {
"1k": 1024,
"2k": 2048,
"4k": 4096
};
const DEFAULT_STATE = {
model: "4.5",
quality: "2k",
ratio: "1:1"
};
let state = { ...DEFAULT_STATE };
const modelChips = document.querySelectorAll(".chip[data-model]");
const qualityBtns = document.querySelectorAll(".segment-btn[data-quality]");
const ratioBtns = document.querySelectorAll(".ratio-btn[data-ratio]");
const dimsMainEl = document.getElementById("dimsMain");
const dimsExtraEl = document.getElementById("dimsExtra");
const resetBtn = document.getElementById("resetBtn");
const saveBtn = document.getElementById("saveBtn");
const statusText = document.getElementById("statusText");
const statusPill = document.getElementById("statusPill");
function sanitizeState(raw) {
const s = { ...DEFAULT_STATE, ...(raw || {}) };
if (!["4.5", "4.0"].includes(String(s.model))) {
s.model = DEFAULT_STATE.model;
}
if (!["1k", "2k", "4k"].includes(String(s.quality))) {
s.quality = DEFAULT_STATE.quality;
}
if (!Object.prototype.hasOwnProperty.call(RATIO_BASE_2K, s.ratio)) {
s.ratio = DEFAULT_STATE.ratio;
}
return s;
}
function computeDims(ratio, quality) {
const base = RATIO_BASE_2K[ratio] || RATIO_BASE_2K["1:1"];
const scale = quality === "1k" ? 0.5 : quality === "4k" ? 2 : 1;
return {
w: Math.round(base.w * scale),
h: Math.round(base.h * scale)
};
}
function updateDimsUI() {
const dims = computeDims(state.ratio, state.quality);
dimsMainEl.innerHTML =
'Размер: <strong>' + dims.w + ' × ' + dims.h + "</strong>";
const le = LONG_EDGE_BY_QUALITY[state.quality] || LONG_EDGE_BY_QUALITY["2k"];
dimsExtraEl.textContent = "Long edge (i2i): " + le;
}
function applyStateToUI() {
modelChips.forEach(ch => {
ch.classList.toggle("active", ch.dataset.model === String(state.model));
});
qualityBtns.forEach(btn => {
btn.classList.toggle("active", btn.dataset.quality === String(state.quality));
});
ratioBtns.forEach(btn => {
btn.classList.toggle("active", btn.dataset.ratio === String(state.ratio));
});
updateDimsUI();
}
function setStatus(text, type) {
statusText.textContent = text;
statusPill.classList.remove("ok", "err");
if (type === "ok") {
statusPill.textContent = "cloud";
statusPill.classList.add("ok");
} else if (type === "err") {
statusPill.textContent = "local";
statusPill.classList.add("err");
} else {
statusPill.textContent = "offline";
}
}
function buildPayload() {
const dims = computeDims(state.ratio, state.quality);
const longEdge = LONG_EDGE_BY_QUALITY[state.quality] || LONG_EDGE_BY_QUALITY["2k"];
return {
type: "seedream_settings",
model: state.model,
quality: state.quality,
ratio: state.ratio,
dims_t2i: dims,
long_edge_i2i: longEdge
};
}
function saveToLocal(payload) {
try {
localStorage.setItem(LS_KEY, JSON.stringify(payload));
} catch (e) {
console.warn("localStorage error", e);
}
}
function loadFromLocal() {
try {
const raw = localStorage.getItem(LS_KEY);
if (!raw) return null;
return JSON.parse(raw);
} catch (e) {
console.warn("localStorage read error", e);
return null;
}
}
function saveSettings() {
const payload = buildPayload();
setStatus("Сохраняю…", null);
const finalize = (storedToCloud) => {
saveToLocal(payload);
if (storedToCloud) {
setStatus("Сохранено (Cloud + local)", "ok");
} else {
setStatus("Сохранено (local)", "err");
}
if (tg && typeof tg.sendData === "function") {
try {
tg.sendData(JSON.stringify(payload));
} catch (e) {
console.error("sendData error", e);
}
}
setTimeout(() => {
if (tg) tg.close();
}, 160);
};
if (tg && tg.CloudStorage && typeof tg.CloudStorage.setItem === "function") {
try {
tg.CloudStorage.setItem(
CS_KEY,
JSON.stringify(payload),
function (err, stored) {
if (err) {
console.warn("CloudStorage error", err);
finalize(false);
} else {
finalize(!!stored);
}
}
);
} catch (e) {
console.warn("CloudStorage exception", e);
finalize(false);
}
} else {
finalize(false);
}
}
function restoreState() {
// 1) CloudStorage, если доступен
if (tg && tg.CloudStorage && typeof tg.CloudStorage.getItem === "function") {
try {
tg.CloudStorage.getItem(CS_KEY, function (err, value) {
if (err || !value) {
const local = loadFromLocal();
state = sanitizeState(local);
applyStateToUI();
setStatus(local ? "Загружено из localStorage" : "Настройки по умолчанию", null);
} else {
try {
const parsed = JSON.parse(value);
state = sanitizeState(parsed);
} catch {
const local = loadFromLocal();
state = sanitizeState(local);
}
applyStateToUI();
setStatus("Загружено из CloudStorage", "ok");
}
});
} catch (e) {
console.warn("CloudStorage get exception", e);
const local = loadFromLocal();
state = sanitizeState(local);
applyStateToUI();
setStatus(local ? "Загружено из localStorage" : "Настройки по умолчанию", null);
}
} else {
const local = loadFromLocal();
state = sanitizeState(local);
applyStateToUI();
setStatus(local ? "Загружено из localStorage" : "Настройки по умолчанию", null);
}
}
modelChips.forEach(ch => {
ch.addEventListener("click", () => {
state.model = ch.dataset.model;
applyStateToUI();
});
});
qualityBtns.forEach(btn => {
btn.addEventListener("click", () => {
state.quality = btn.dataset.quality;
applyStateToUI();
});
});
ratioBtns.forEach(btn => {
btn.addEventListener("click", () => {
state.ratio = btn.dataset.ratio;
applyStateToUI();
});
});
resetBtn.addEventListener("click", () => {
state = { ...DEFAULT_STATE };
applyStateToUI();
setStatus("Сброшено к умолчанию", null);
});
saveBtn.addEventListener("click", () => {
saveSettings();
});
restoreState();
})();
</script>
</body>
</html>