// 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' && (
Опишите тему