import React, { useState, useEffect } from "react"; import { FileText, Plus, RotateCcw, Send, Settings, BookOpen, Code2, Eye, AlertTriangle, Trash2, Check, ArrowRight, Gauge, Bookmark, Newspaper, Clock, Zap, RefreshCw, Calendar } from "lucide-react"; import { initialMockPosts } from "../data/mockPosts"; import { WPPost, GeneratorSettings, SimulationResult } from "../types"; export default function NewsletterSimulator() { const [posts, setPosts] = useState(initialMockPosts); const [generatorMode, setGeneratorMode] = useState<"autopilot" | "summarize">("autopilot"); const [activeTab, setActiveTab] = useState<"articles" | "settings" | "images">("settings"); const [resultTab, setResultTab] = useState<"reader" | "raw" | "seo">("reader"); const [loading, setLoading] = useState(false); const [loadingStep, setLoadingStep] = useState(""); const [error, setError] = useState(null); // Settings state const [settings, setSettings] = useState({ target_themes: "the arts, creative leadership, creative entrepreneurship, food security, AI", default_post_status: "draft", ai_instructions: "Write a high-quality weekly digest post exploring creative community hubs, Canadian arts events, local food security coalitions, and civic technologies. Supply concrete insights, maintain Canadian English spellings (use matching words like colour, centre), and do not use AI clichés like landscape, delve, or masterclass.", limit_days: 7, model_name: "gemini-3.5-flash", schedule_hours: 24, intro_text: "Arts Incubator Community & Creative Hub updates.", extro_text: "To subscribe to our printed bulletin or support the incubator programs, visit artsincubator.org.", author_id: 1, image_urls_list: `https://images.unsplash.com/photo-1460661419201-fd4cecdf8a8b?q=80&w=800 https://images.unsplash.com/photo-1513829096999-4978602294fc?q=80&w=800 https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?q=80&w=800 https://images.unsplash.com/photo-1531482615713-2afd69097998?q=80&w=800 https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?q=80&w=800`, title_words: "5-7", subtitle_words: "6-10", excerpt_words: 20, article_ideas_list: `The intersection of traditional arts and modern food security initiatives. How creative leadership is reshaping the tech landscape in North America. The role of community-driven design in urban agriculture centers. Emerging trends in digital storytelling for cultural preservation. The importance of sustainable materials in large-scale art installations.` }); const [simulatedIdeas, setSimulatedIdeas] = useState([]); // Parse ideas list on load or settings change useEffect(() => { if (settings.article_ideas_list) { const lines = settings.article_ideas_list.split('\n').filter(l => l.trim().length > 0); const parsed = lines.map((line, idx) => { const parts = line.split(','); return { id: idx + 1, idea: parts[0]?.trim() || '', lastused: parts[1]?.trim() || null }; }); setSimulatedIdeas(parsed); } }, [settings.article_ideas_list]); // Autopilot simulated Cron logs const [scheduleLogs, setScheduleLogs] = useState([ "[System Autopilot Booted] WordPress schedule_hours initialized.", "WordPress Cron successfully registered event hook: 'summarybot_cron_hook'.", "Current configuration: Independent prompt generation triggers every 24 hours.", "Status: Idle. Waiting for scheduled interval trigger or manual override." ]); // Simulated Images database for live preview and rotation cooldown checks const [simulatedImages, setSimulatedImages] = useState<{ id: number; url: string; lastused: string | null; }[]>([ { id: 1, url: 'https://images.unsplash.com/photo-1460661419201-fd4cecdf8a8b?q=80&w=800', lastused: null }, { id: 2, url: 'https://images.unsplash.com/photo-1513829096999-4978602294fc?q=80&w=800', lastused: null }, { id: 3, url: 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?q=80&w=800', lastused: null }, { id: 4, url: 'https://images.unsplash.com/photo-1531482615713-2afd69097998?q=80&w=800', lastused: null }, { id: 5, url: 'https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?q=80&w=800', lastused: null }, ]); const [newImageUrl, setNewImageUrl] = useState(""); const [csvPasteText, setCsvPasteText] = useState(""); const [simulationStatus, setSimulationStatus] = useState(""); const [editingImageId, setEditingImageId] = useState(null); const [editingUrl, setEditingUrl] = useState(""); const [editingLastUsed, setEditingLastUsed] = useState(""); const handleAddImageUrl = () => { if (!newImageUrl.trim()) return; const nextId = simulatedImages.length > 0 ? Math.max(...simulatedImages.map(img => img.id)) + 1 : 1; setSimulatedImages([ ...simulatedImages, { id: nextId, url: newImageUrl.trim(), lastused: null } ]); setNewImageUrl(""); setSimulationStatus("Successfully added 1 new image banner."); }; const handleImportCSV = () => { if (!csvPasteText.trim()) return; // Format: URL, timestamp (line by line) const lines = csvPasteText.split("\n").map(l => l.trim()).filter(Boolean); let count = 0; const newImgs = [...simulatedImages]; let nextId = newImgs.length > 0 ? Math.max(...newImgs.map(img => img.id)) + 1 : 1; for (const line of lines) { if (line.includes(",")) { const parts = line.split(",").map(p => p.trim()); const u = parts[0]; const t = parts[1] || null; if (u) { newImgs.push({ id: nextId++, url: u, lastused: t }); count++; } } else { newImgs.push({ id: nextId++, url: line, lastused: null }); count++; } } setSimulatedImages(newImgs); setCsvPasteText(""); setSimulationStatus(`Successfully imported ${count} images from CSV paste.`); }; const handleStartEditImage = (img: { id: number; url: string; lastused: string | null }) => { setEditingImageId(img.id); setEditingUrl(img.url); setEditingLastUsed(img.lastused || ""); }; const handleSaveEditImage = () => { if (!editingUrl.trim()) return; setSimulatedImages(simulatedImages.map(img => { if (img.id === editingImageId) { return { ...img, url: editingUrl.trim(), lastused: editingLastUsed.trim() || null }; } return img; })); setEditingImageId(null); setSimulationStatus("Successfully updated image settings."); }; const handleDeleteSimulatedImage = (id: number) => { setSimulatedImages(simulatedImages.filter(img => img.id !== id)); setSimulationStatus("Successfully deleted image option."); if (editingImageId === id) { setEditingImageId(null); } }; // Simulated state for new post form const [showAddForm, setShowAddForm] = useState(false); const [newTitle, setNewTitle] = useState(""); const [newCategory, setNewCategory] = useState("Creative Leadership"); const [newContent, setNewContent] = useState(""); // Results const [result, setResult] = useState(null); // Sync activeTab side effect if mode shifts useEffect(() => { if (generatorMode === "autopilot") { setActiveTab("settings"); } else { setActiveTab("articles"); } }, [generatorMode]); const resetPosts = () => { setPosts(initialMockPosts); setError(null); }; const handleAddPost = (e: React.FormEvent) => { e.preventDefault(); if (!newTitle.trim() || !newContent.trim()) return; const newPost: WPPost = { id: "custom-" + Date.now(), title: newTitle, url: "https://artsincubator.ca/articles/" + newTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-"), author: "Guest Editor", category: newCategory, date: new Date().toISOString().split("T")[0], content: newContent }; setPosts([newPost, ...posts]); setNewTitle(""); setNewContent(""); setShowAddForm(false); }; const handleDeletePost = (id: string) => { setPosts(posts.filter(p => p.id !== id)); }; const triggerGeneration = async () => { setLoading(true); setError(null); setResult(null); const steps = generatorMode === "autopilot" ? [ "Autopilot cron trigger received...", `Retrieving custom prompt guidelines: "${settings.ai_instructions.substring(0, 45)}..."`, `Configuring ambient context tags (Intro: "${settings.intro_text || ''}", Extro: "${settings.extro_text || ''}")`, `Establishing proxy link to backend server Gemini ${settings.model_name}...`, "Writing and parsing content block structure...", "Simulated draft post created in WordPress database successfully!" ] : [ "Quering published articles across the local database...", "Identified " + posts.length + " active articles from the past " + settings.limit_days + " days.", "Parsing categories & matching targets: " + settings.target_themes + "...", "Packaging prompts and instructions for Gemini " + settings.model_name + "...", "Invoking secure full-stack server proxy for Gemini API...", "Structuring summaries within Gutenberg Block wrappers...", "Draft post metadata verified successfully. Compiling views..." ]; let stepIdx = 0; setLoadingStep(steps[0]); if (generatorMode === "autopilot") { setScheduleLogs(prev => [ ...prev, `[On-Demand Force Run] Initiating autonomous generation cycle...`, `Contacting Gemini ${settings.model_name} with Core Guidelines: "${settings.ai_instructions.substring(0, 55)}..."` ]); } const interval = setInterval(() => { stepIdx++; if (stepIdx < steps.length) { setLoadingStep(steps[stepIdx]); } }, 1100); try { // Pick an Idea satisfaction rotation (Cooldown 6 weeks simulated) const nowTimes = new Date(); const sixWeeksAgoIdea = new Date(nowTimes.getTime() - (6 * 7 * 24 * 60 * 60 * 1000)); let availableIdeas = simulatedIdeas.filter(i => !i.lastused || new Date(i.lastused) < sixWeeksAgoIdea); if (availableIdeas.length === 0) { availableIdeas = [...simulatedIdeas].sort((a, b) => { const dateA = a.lastused ? new Date(a.lastused).getTime() : 0; const dateB = b.lastused ? new Date(b.lastused).getTime() : 0; return dateA - dateB; }); } const selectedIdea = availableIdeas[Math.floor(Math.random() * availableIdeas.length)]; if (selectedIdea) { // Update local simulated state to show usage const updatedIdeas = simulatedIdeas.map(i => i.id === selectedIdea.id ? { ...i, lastused: nowTimes.toISOString().split('T')[0] } : i); setSimulatedIdeas(updatedIdeas); // Persist back to text settings (simulation only) const newIdeaListStr = updatedIdeas.map(i => `${i.idea}${i.lastused ? `, ${i.lastused}` : ""}`).join("\n"); setSettings(prev => ({ ...prev, article_ideas_list: newIdeaListStr })); } const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ posts: generatorMode === "autopilot" ? [] : posts, themes: settings.target_themes, instructions: settings.ai_instructions, model_name: settings.model_name, intro: settings.intro_text, extro: settings.extro_text, author_id: settings.author_id, image_urls_list: settings.image_urls_list, simulated_images: simulatedImages, // Pass simulated images to server side image rotators! title_words: settings.title_words, subtitle_words: settings.subtitle_words, excerpt_words: settings.excerpt_words, narrative_idea: selectedIdea?.idea || "" }) }); const data = await response.json(); clearInterval(interval); if (!response.ok) { throw new Error(data.error || "Failed secure server-side compile."); } // Update lastused timestamp in simulation database state if a simulated image was picked if (data.chosen_image_id) { setSimulatedImages(prev => prev.map(img => { if (img.id === data.chosen_image_id) { return { ...img, lastused: new Date().toISOString() }; } return img; })); } const mockAuthors: Record = { 1: "Primary Administrator (ID: 1)", 2: "Guest Editor (ID: 2)", 3: "Arts Incubator Lead (ID: 3)", 4: "Community Reporter (ID: 4)" }; const authorName = mockAuthors[settings.author_id || 1] || "Primary Administrator (ID: 1)"; setResult({ title: data.title, excerpt: data.excerpt, category: data.category || "General Hub", content: data.content, originalRaw: data.originalRaw, author: authorName }); setResultTab("reader"); if (generatorMode === "autopilot") { const imageLogStr = data.chosen_image_id ? `[Image Selected] Block banner Image Option #${data.chosen_image_id} selected. Cooldown rule updated.` : `[Image Selected] No eligible banner image selected.`; setScheduleLogs(prev => [ ...prev, `[Success] Dynamic draft written: "${data.title}"`, imageLogStr, `Gutenberg HTML comment tags successfully structured inside simulated database. Status: '${settings.default_post_status}'`, `Next cron automation scheduled in ${settings.schedule_hours} hours hands-free.` ]); } } catch (err: any) { clearInterval(interval); console.error(err); setError( err.message || "Something went wrong contacting the server backend API." ); if (generatorMode === "autopilot") { setScheduleLogs(prev => [ ...prev, `[Error] Autopilot execution aborted: ${err.message || "API connection timeout"}`, `[System] Failure detected. Automated retry scheduled for 3 minutes from now.` ]); } } finally { setLoading(false); } }; // Utility to convert Gutenberg comments to clean HTML for the simulator preview const renderPreviewHTML = (rawBlocksContent: string) => { let html = rawBlocksContent; html = html.replace(/([\s\S]*?)/g, "$1"); html = html.replace(/([\s\S]*?)/g, "$1"); html = html.replace(/([\s\S]*?)/g, "$1"); html = html.replace(/([\s\S]*?)/g, "$1"); html = html.replace(/([\s\S]*?)/g, "$1"); return { __html: html }; }; // Basic SEO metrics scorecard calculation const getSEOMetrics = () => { if (!result) return { textLength: 0, urlCount: 0, densityOk: false, headingOk: false }; const content = result.content; const textLength = content.replace(/<[^>]*>/g, "").split(/\s+/).length; // Count occurrences of links const linkMatches = content.match(/ 150; return { textLength, urlCount, densityOk, headingOk }; }; const seo = getSEOMetrics(); const handleHoursChange = (hours: number) => { setSettings(prev => ({ ...prev, schedule_hours: hours })); setScheduleLogs(prev => [ ...prev, `[Config Updated] Schedule interval adjusted to: Every ${hours} hours.`, `[WP-Cron Hook Rescheduled] Resetting hook event 'summarybot_cron_hook' offset.` ]); }; return (
{/* Mode selectors at the top */}
SB

SummaryBot WordPress Integration v2.0.0

Simulated panel for autonomous newsletter drafting & WP-cron scheduling.

{/* Visual Workspace configuration */}
{/* Left column: Setup workspace */}
{/* Nav Headers depending on mode */}
{generatorMode === "summarize" ? ( <> ) : ( <> )}
{/* Tab Content 1: Articles List (Manual summarizes only) */} {generatorMode === "summarize" && activeTab === "articles" && (
Simulated WP Database:
{/* Add form */} {showAddForm && (

Simulate Article Addition

setNewTitle(e.target.value)} placeholder="Article Title (e.g. Scaling Food Distribution Networks)" className="w-full text-xs text-slate-800 bg-white border border-slate-200 rounded px-2.5 py-2 focus:border-[#2271b1] focus:ring-1 focus:ring-[#2271b1] focus:outline-none" />
WP url auto-generated