'use client'; import { useState, useEffect, useCallback } from 'react'; import { Plus, Sparkles, RefreshCw, Loader2, Trash2, Edit3, Save, X, ChevronDown, ChevronRight, Play, Settings, Calendar, Clock, BookOpen, Zap, Tag, RotateCcw, Check, AlertCircle, } from 'lucide-react'; // ─── Константы ──────────────────────────────────────────────────────────────── const COLORS = [ { key: 'indigo', cls: 'bg-indigo-50 dark:bg-indigo-950 border-indigo-200 dark:border-indigo-800 text-indigo-700 dark:text-indigo-300' }, { key: 'emerald', cls: 'bg-emerald-50 dark:bg-emerald-950 border-emerald-200 dark:border-emerald-800 text-emerald-700 dark:text-emerald-300' }, { key: 'amber', cls: 'bg-amber-50 dark:bg-amber-950 border-amber-200 dark:border-amber-800 text-amber-700 dark:text-amber-300' }, { key: 'red', cls: 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800 text-red-700 dark:text-red-300' }, { key: 'purple', cls: 'bg-purple-50 dark:bg-purple-950 border-purple-200 dark:border-purple-800 text-purple-700 dark:text-purple-300' }, { key: 'blue', cls: 'bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-300' }, { key: 'cyan', cls: 'bg-cyan-50 dark:bg-cyan-950 border-cyan-200 dark:border-cyan-800 text-cyan-700 dark:text-cyan-300' }, { key: 'orange', cls: 'bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800 text-orange-700 dark:text-orange-300' }, { key: 'rose', cls: 'bg-rose-50 dark:bg-rose-950 border-rose-200 dark:border-rose-800 text-rose-700 dark:text-rose-300' }, { key: 'neutral', cls: 'bg-neutral-50 dark:bg-neutral-900 border-neutral-200 dark:border-neutral-700 text-neutral-700 dark:text-neutral-300' }, ]; const colorCls = (k) => (COLORS.find(c => c.key === k) || COLORS[0]).cls; const GENRE_LABELS = { tutorial: { label: 'Туториал', color: 'bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300' }, comparison: { label: 'Сравнение', color: 'bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300' }, opinion: { label: 'Мнение', color: 'bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300' }, digest: { label: 'Дайджест', color: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300' }, case: { label: 'Кейс', color: 'bg-rose-100 text-rose-700 dark:bg-rose-950 dark:text-rose-300' }, news: { label: 'Новость', color: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-950 dark:text-cyan-300' }, }; function detectGenre(topic) { const m = topic?.match(/^\[([^\]]+)\]/); if (!m) return null; const map = { 'ТУТОРИАЛ':'tutorial','СРАВНЕНИЕ':'comparison','МНЕНИЕ':'opinion','ДАЙДЖЕСТ':'digest','КЕЙС':'case','НОВОСТЬ':'news' }; return map[m[1].toUpperCase()] || null; } function GenreBadge({ genre }) { if (!genre) return null; const g = GENRE_LABELS[genre]; if (!g) return null; return {g.label}; } // ─── Главный компонент ──────────────────────────────────────────────────────── export default function AutogenTab({ channel }) { const [settings, setSettings] = useState(null); const [categories, setCategories] = useState([]); const [todayDrafts, setTodayDrafts] = useState([]); const [rotation, setRotation] = useState([]); const [loading, setLoading] = useState(true); const [toast, setToast] = useState(null); // Состояние создания категории const [showNewCat, setShowNewCat] = useState(false); const [newCat, setNewCat] = useState({ slug:'', name:'', icon:'📝', color:'indigo', description:'' }); const [savingCat, setSavingCat] = useState(false); // Развёрнутая категория (темы) const [expandedCatId, setExpandedCatId] = useState(null); // Настройки autogen const [showSettings, setShowSettings] = useState(false); const [autogenForm, setAutogenForm] = useState({ enabled: false, posts_per_day: 3, run_hour: 10, run_minute: 0 }); const [savingSettings, setSavingSettings] = useState(false); // Запуск генерации const [running, setRunning] = useState(false); const flash = (msg, type='success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3500); }; const load = useCallback(async () => { setLoading(true); try { const [statusRes, catsRes, draftsRes, rotRes] = await Promise.allSettled([ fetch(`/api/engine/channels/${channel.id}/autogen`), fetch(`/api/engine/channels/${channel.id}/categories`), fetch(`/api/engine/channels/${channel.id}/autogen/today`), fetch(`/api/engine/channels/${channel.id}/autogen/rotation?days=7`), ]); if (statusRes.status === 'fulfilled' && statusRes.value.ok) { const d = await statusRes.value.json(); setSettings(d); setAutogenForm({ enabled: !!d.enabled, posts_per_day: d.posts_per_day||3, run_hour: d.run_hour||10, run_minute: d.run_minute||0 }); } if (catsRes.status === 'fulfilled' && catsRes.value.ok) { const d = await catsRes.value.json(); setCategories(d.items || []); } if (draftsRes.status === 'fulfilled' && draftsRes.value.ok) { const d = await draftsRes.value.json(); setTodayDrafts(d.drafts || []); } if (rotRes.status === 'fulfilled' && rotRes.value.ok) { const d = await rotRes.value.json(); setRotation(d.preview || []); } } catch (e) { flash('Ошибка загрузки: ' + e.message, 'error'); } setLoading(false); }, [channel.id]); useEffect(() => { load(); }, [load]); // ── Сохранить настройки autogen ────────────────────────────────────────── async function saveAutogenSettings() { setSavingSettings(true); try { const r = await fetch(`/api/engine/channels/${channel.id}/autogen/enable`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(autogenForm), }); if (!r.ok) throw new Error((await r.json()).error || 'fail'); flash('Настройки сохранены'); setShowSettings(false); await load(); } catch (e) { flash(e.message, 'error'); } setSavingSettings(false); } // ── Создать категорию ───────────────────────────────────────────────────── async function createCategory() { if (!newCat.name.trim() || !newCat.slug.trim()) return; setSavingCat(true); try { const r = await fetch(`/api/engine/channels/${channel.id}/categories`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newCat), }); const d = await r.json(); if (!r.ok) throw new Error(d.error || 'fail'); flash(`Категория «${newCat.name}» создана`); setNewCat({ slug:'', name:'', icon:'📝', color:'indigo', description:'' }); setShowNewCat(false); await load(); } catch (e) { flash(e.message, 'error'); } setSavingCat(false); } // ── Запустить генерацию вручную ──────────────────────────────────────────── async function runGeneration(categoryId = null) { setRunning(true); try { const r = await fetch(`/api/engine/channels/${channel.id}/autogen/run`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(categoryId ? { category_id: categoryId } : {}), }); if (!r.ok) throw new Error((await r.json()).error || 'fail'); flash('Генерация запущена — черновики появятся через ~30 сек'); setTimeout(() => load(), 35000); } catch (e) { flash(e.message, 'error'); } setRunning(false); } if (loading) return (
{settings?.enabled ? `Включена · ${postsPerDay} поста/день · ${String(settings.run_hour||10).padStart(2,'0')}:${String(settings.run_minute||0).padStart(2,'0')}` : 'Выключена — настрой и включи'}
Скользящее окно {postsPerDay} из {categories.filter(c=>c.is_active).length} категорий — каждый день другой набор.