// Generate flow: source choice → topic + style → loading → handoff to preview.
const { useState: useStateG, useEffect: useEffectG } = React;
const STYLES = [
{ id: 'minimal', name: 'Минималистичный', sub: 'Большая типографика, много воздуха' },
{ id: 'graphic', name: 'Графический', sub: 'Цветные плашки, активные акценты' },
{ id: 'photo', name: 'С фото', sub: 'Фон-фото, текст поверх' },
];
const PROPOSED_TOPICS = (org) => {
const map = {
studio: [
{ title: 'Палитра 2026: почему все вернулись к охре', src: '@designpub · 12 мин назад' },
{ title: 'Брендинг без AI — манифест на 2026 год', src: 'itsnicethat.com · 2 ч назад' },
{ title: 'Седой минимализм возвращается в lookbook', src: 'sidebar.io · 1 ч назад' },
{ title: 'Как Studio Feixen работают с типографикой', src: '@gloomyowl · 4 ч назад' },
],
finlab: [
{ title: 'AI в финтехе: где реальный ROI, а где гипертайп', src: '@finsight · 8 мин назад' },
{ title: 'Цифровой рубль — что меняется для бизнеса', src: 'ft.com · 22 мин назад' },
{ title: 'Embedded finance в 2026: 5 кейсов', src: 'a16z.com · 1 ч назад' },
{ title: 'BNPL по итогам года: рост или плато', src: 'thefintechtimes.com · 3 ч назад' },
],
velo: [
{ title: 'Подбор седла на 200 км — гайд', src: 'cyclingtips.com · 35 мин назад' },
{ title: 'Bikepacking за выходные: маршруты Подмосковья', src: '@velolife · 7 мин назад' },
{ title: 'Шлем за 5к против шлема за 25к — есть ли разница', src: 'cyclingtips.com · 2 ч назад' },
],
};
return map[org.id] || map.studio;
};
function Generate({ org, data, onGenerated, prefill }) {
const { Button, IconButton, Card, TextInput, Textarea, Pill } = window.UI;
const I = window.Icons;
const [step, setStep] = useStateG(prefill && prefill.topic ? 1 : 0);
const [source, setSource] = useStateG(prefill ? prefill.source : null); // 'sources' | 'ideas' | 'custom' | 'all-sources'
const [picked, setPicked] = useStateG(prefill && prefill.topic ? prefill.topic : null);
const [custom, setCustom] = useStateG('');
const [customRec, setCustomRec] = useStateG(false);
const [customRecT, setCustomRecT] = useStateG(0);
const [style, setStyle] = useStateG('minimal');
const [prompt, setPrompt] = useStateG('');
const [promptRec, setPromptRec] = useStateG(false);
const [promptRecT, setPromptRecT] = useStateG(0);
const [loadingStep, setLoadingStep] = useStateG(0);
const proposed = PROPOSED_TOPICS(org);
// recording timers
useEffectG(() => {
if (!customRec) return;
const t = setInterval(() => setCustomRecT((v) => v + 1), 1000);
return () => clearInterval(t);
}, [customRec]);
useEffectG(() => {
if (!promptRec) return;
const t = setInterval(() => setPromptRecT((v) => v + 1), 1000);
return () => clearInterval(t);
}, [promptRec]);
// Loading
useEffectG(() => {
if (step !== 3) return;
setLoadingStep(0);
const labels = 5;
const t = setInterval(() => setLoadingStep((s) => {
if (s + 1 >= labels) {
clearInterval(t);
setTimeout(() => onGenerated({ topic: picked || { title: custom, src: source === 'all-sources' ? 'из всех источников' : 'своя тема' }, style, prompt }), 500);
return s + 1;
}
return s + 1;
}), 700);
return () => clearInterval(t);
}, [step]);
const goConfirm = (topic) => { setPicked(topic); setStep(1); };
const stopCustomRec = () => {
if (customRecT > 0) setCustom((custom ? custom + ' ' : '') + `[голос ${Math.floor(customRecT/60)}:${String(customRecT%60).padStart(2,'0')}] расшифровка появится здесь…`);
setCustomRec(false); setCustomRecT(0);
};
const stopPromptRec = () => {
if (promptRecT > 0) setPrompt((prompt ? prompt + ' ' : '') + `[голос ${Math.floor(promptRecT/60)}:${String(promptRecT%60).padStart(2,'0')}] расшифровка появится здесь…`);
setPromptRec(false); setPromptRecT(0);
};
return (
{/* Steps header */}
{['Источник', 'Тема и стиль', 'Промпт', 'Готово'].map((l, i) => (
{i < step ? : i + 1}
{l}{i === 2 && · опц.}
{i < 3 && }
))}
{step === 0 && (
Откуда возьмём идею?
Выберите начало — дальше Omneee соберёт пост.
{/* Sources */}
{/* Ideas */}
{/* Custom */}
{/* Pickers */}
{source === 'sources' && (
{/* Use-all CTA */}
или выберите одну из {proposed.length} свежих тем
обновлено сейчас
{proposed.map((t, i) => (
))}
)}
{source === 'ideas' && (
Из вашего бэклога
{data.ideas.slice(0, 6).map((idea, i) => {
const Icon = idea.type === 'text' ? I.Text : idea.type === 'link' ? I.Link : I.Mic;
return (
);
})}
)}
{source === 'custom' && (
Опишите тему
{customRec && (
{Array.from({ length: 28 }).map((_, i) => (
))}
)}
Хотите конкретные пункты — перечислите их.
)}
)}
{step === 1 && (
Тема
{picked ? picked.title : custom}
{picked &&
{picked.src}
}
Стиль карусели
{STYLES.map((s) => (
))}
Подкрутить
{['Тон бренда', 'Без эмодзи', '6–8 слайдов', 'Заголовок цифрой', 'Цитата в финале'].map((t) => (
{t}
))}
{/* mock brand summary */}
из брендбука {org.name}
{org.brand.colors.map((c, i) => (
))}
Заголовки · {org.brand.fonts[0]}
Текст · {org.brand.fonts[1] || org.brand.fonts[0]}
«{org.brand.tone}»
)}
{step === 2 && (
Шаг 03 · опционально
Что-то добавить от себя?
Промпт, акцент, фразы, которые точно должны быть. Можно надиктовать голосом — или пропустить.
{promptRec && (
{Array.from({ length: 36 }).map((_, i) => (
))}
)}
подсказки:
{[
'Без эмодзи',
'Жёстче тон',
'Добавь цифру в заголовок',
'Сделай провокационно',
'Цитата в финале',
].map((t) => (
setPrompt((prompt ? prompt + ' ' : '') + t.toLowerCase() + '. ')}>+ {t}
))}
)}
{step === 3 && (
{/* Orbiting sparkles */}
Генерируем карусель…
Это займёт около минуты. Можно закрыть страницу — пришлём в Telegram.
{[
'Читаем источник',
'Подбираем тезисы по тону бренда',
'Складываем структуру слайдов',
'Применяем цвета и шрифты',
'Финальная вёрстка',
].map((l, i) => (
{i < loadingStep ? : i === loadingStep ? : }
{l}
{i === loadingStep &&
~{(5 - i) * 6}s
}
))}
)}
);
}
function StylePreview({ style, brand }) {
const [c1, c2, c3] = [brand.colors[0] || '#000', brand.colors[1] || '#fff', brand.colors[2] || '#7C5CFF'];
if (style === 'minimal') {
return (
);
}
if (style === 'graphic') {
return (
);
}
// photo
return (
);
}
window.Screens = window.Screens || {};
window.Screens.Generate = Generate;