'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import { ArrowLeft, Sparkles, Wand2, Copy, Check, Loader2, Settings, Image as ImageIcon, RefreshCw, Scissors, Maximize2, Zap, Heart, MessageSquare, Pencil, X, ChevronDown, Send, Clock, Trash2 } from 'lucide-react'; const GOAL_LABELS = { educational: 'Обучение', news: 'Новости', entertainment: 'Развлечение', expert: 'Экспертный', sales: 'Продажи', }; const TRANSFORMS = [ { action: 'shorter', label: 'Короче', icon: Scissors, desc: 'Сократить в 2 раза' }, { action: 'longer', label: 'Длиннее', icon: Maximize2, desc: 'Расширить с примерами' }, { action: 'improve', label: 'Улучшить', icon: Sparkles, desc: 'Убрать AI-штампы' }, { action: 'bolder', label: 'Дерзче', icon: Zap, desc: 'Острее формулировки' }, { action: 'softer', label: 'Мягче', icon: Heart, desc: 'Доброжелательней' }, { action: 'addCta', label: 'Призыв', icon: MessageSquare, desc: 'Добавить CTA' }, { action: 'forVk', label: 'Для ВК', icon: RefreshCw, desc: 'Адаптировать под ВКонтакте' }, ]; export default function ChannelView({ channel }) { const [topic, setTopic] = useState(''); const [generating, setGenerating] = useState(false); const [post, setPost] = useState(null); const [error, setError] = useState(''); const [copied, setCopied] = useState(false); const [tokens, setTokens] = useState(null); // Варианты постов (история) const [variants, setVariants] = useState([]); const [editing, setEditing] = useState(false); // Картинка const [image, setImage] = useState(null); const [genImage, setGenImage] = useState(false); // Трансформации const [transforming, setTransforming] = useState(false); // Идеи тем const [showIdeas, setShowIdeas] = useState(false); const [ideas, setIdeas] = useState([]); const [loadingIdeas, setLoadingIdeas] = useState(false); async function fetchIdeas() { setLoadingIdeas(true); setError(''); try { const res = await fetch('/api/generate/topics-ideas', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channelId: channel.id, count: 7 }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка'); setIdeas(data.topics || []); setShowIdeas(true); } catch (err) { setError(err.message); } finally { setLoadingIdeas(false); } } // Сохранение и публикация const [savedPostId, setSavedPostId] = useState(null); const [publishing, setPublishing] = useState(false); const [showScheduler, setShowScheduler] = useState(false); const [scheduleAt, setScheduleAt] = useState(''); const [history, setHistory] = useState([]); const [loadingHistory, setLoadingHistory] = useState(false); // Подгрузка истории при монтировании useEffect(() => { loadHistory(); }, []); async function loadHistory() { setLoadingHistory(true); try { const res = await fetch(`/api/user-posts?channel_id=${channel.id}&limit=20`); const data = await res.json(); if (Array.isArray(data)) setHistory(data); } catch {} finally { setLoadingHistory(false); } } async function savePost(status = 'draft', scheduledAt = null) { if (!post) return; setPublishing(true); setError(''); try { let id = savedPostId; if (!id) { // Создаём const res = await fetch('/api/user-posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel_id: channel.id, content: post, image_url: image, topic: topic.trim(), status, scheduled_at: scheduledAt, }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка'); id = data.id; setSavedPostId(id); } else { // Обновляем const res = await fetch(`/api/user-posts/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: post, image_url: image, status, scheduled_at: scheduledAt }), }); if (!res.ok) throw new Error((await res.json()).error || 'Ошибка'); } await loadHistory(); return id; } catch (err) { setError(err.message); return null; } finally { setPublishing(false); } } async function publishNow() { const id = await savePost('draft'); if (!id) return; setPublishing(true); try { const res = await fetch(`/api/user-posts/${id}/publish`, { method: 'POST' }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка'); await loadHistory(); setPost(null); setSavedPostId(null); setImage(null); setTopic(''); } catch (err) { setError(err.message); } finally { setPublishing(false); } } async function schedule() { if (!scheduleAt) return setError('Укажите время'); const id = await savePost('scheduled', new Date(scheduleAt).toISOString()); if (!id) return; setShowScheduler(false); setScheduleAt(''); setPost(null); setSavedPostId(null); setImage(null); setTopic(''); } async function generate(asVariant = false) { if (!topic.trim() && !asVariant) return; if (asVariant && !post) return; setGenerating(true); setError(''); const useTopic = asVariant ? `${topic} (вариант ${variants.length + 2})` : topic.trim(); try { const createRes = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'post', channelId: channel.id, topic: useTopic, useCritique: true, }), }); const job = await createRes.json(); if (!createRes.ok) throw new Error(job.error || 'Ошибка'); let final; for (let i = 0; i < 60; i++) { await new Promise(r => setTimeout(r, 2000)); const r = await fetch(`/api/generate/${job.jobId}`); const j = await r.json(); if (j.status === 'done' || j.status === 'failed') { final = j; break; } } if (!final) throw new Error('Таймаут — попробуй ещё раз'); if (final.status === 'failed') throw new Error(final.error || 'Генерация упала'); // Сохраняем предыдущий вариант в variants if (asVariant && post) { setVariants(v => [...v, { content: post, tokens, image }]); } setPost(final.result); setTokens({ in: final.tokens_in, out: final.tokens_out }); setImage(null); // сбрасываем картинку при новом посте } catch (err) { setError(err.message); } finally { setGenerating(false); } } async function transform(action) { if (!post || transforming) return; setTransforming(true); setError(''); try { const res = await fetch('/api/generate/transform', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channelId: channel.id, originalPost: post, action }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка'); // Сохраняем текущий в варианты setVariants(v => [...v, { content: post, tokens, image }]); setPost(data.content); setImage(null); } catch (err) { setError(err.message); } finally { setTransforming(false); } } async function generateImage() { if (!post || genImage) return; setGenImage(true); setError(''); try { const res = await fetch('/api/generate/post-image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channelId: channel.id, post }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка генерации картинки'); setImage(data.url); } catch (err) { setError(err.message); } finally { setGenImage(false); } } function restoreVariant(idx) { const v = variants[idx]; setVariants(arr => { const next = arr.filter((_, i) => i !== idx); next.push({ content: post, tokens, image }); return next; }); setPost(v.content); setTokens(v.tokens); setImage(v.image); } async function copy() { await navigator.clipboard.writeText(post); setCopied(true); setTimeout(() => setCopied(false), 2000); } return (
К списку каналов

{channel.name}

{GOAL_LABELS[channel.goal] || channel.goal}
{channel.niche &&

{channel.niche}

}
Настройки
{/* Generator */}

Сгенерировать пост

{/* Список идей */} {showIdeas && ideas.length > 0 && (
Идеи для постов
{ideas.map((idea, i) => ( ))}
)}