EBOOKS / server.js
Yogesh
feat: implement colorful HSL book interior themes
b8b22fd
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { HfInference } from '@huggingface/inference';
import fs from 'fs';
import path from 'path';
import { createClient } from '@supabase/supabase-js';
import ws from 'ws';
import { fileURLToPath } from 'url';
import { ZipArchive } from 'archiver';
// Fix for Node.js < 22: inject ws as global WebSocket for Supabase Realtime
if (!globalThis.WebSocket) {
globalThis.WebSocket = ws;
}
// Load environment variables
dotenv.config();
const app = express();
const PORT = process.env.PORT || 5000;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const publicDir = path.join(__dirname, 'public');
const productsDir = path.join(publicDir, 'products');
const coversDir = path.join(publicDir, 'covers');
const packagesDir = path.join(publicDir, 'packages');
if (!fs.existsSync(publicDir)) fs.mkdirSync(publicDir, { recursive: true });
if (!fs.existsSync(productsDir)) fs.mkdirSync(productsDir, { recursive: true });
if (!fs.existsSync(coversDir)) fs.mkdirSync(coversDir, { recursive: true });
if (!fs.existsSync(packagesDir)) fs.mkdirSync(packagesDir, { recursive: true });
// Enable CORS so the React frontend on 5173 can talk to our API on 5000
app.use(cors());
app.use(express.json());
app.use('/static', express.static(publicDir));
// ----------------------------------------------------
// DATABASE CONFIGURATION: SUPABASE WITH JSON FALLBACK
// ----------------------------------------------------
let isSupabaseConnected = false;
let supabase = null;
// Attempt Supabase Connection if credentials are provided
if (process.env.SUPABASE_URL && process.env.SUPABASE_KEY && !process.env.SUPABASE_URL.includes('your-project-id')) {
console.log('πŸ”Œ Connecting to Supabase Cloud Database...');
try {
supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY, {
realtime: { transport: ws }
});
isSupabaseConnected = true;
console.log('βœ… Connected to Supabase Cloud Database!');
} catch (err) {
console.error('❌ Supabase Connection Error:', err.message);
console.log('⚠️ Falling back to Local JSON Database.');
}
} else {
console.log('ℹ️ Supabase credentials not set or using template. Using Local JSON Database.');
}
// JSON File Database Fallback Configuration
const DB_FILE = path.join(process.cwd(), 'db.json');
const readDb = () => {
try {
if (!fs.existsSync(DB_FILE)) {
const defaultCatalog = [
{
id: '1',
title: 'ADHD Daily Focus Study Planner 2026',
subtitle: 'The Ultimate Aesthetic Organization Notebook for Neurodivergent College Students',
type: 'planner',
price: 9.99,
date: new Date().toLocaleDateString()
},
{
id: '2',
title: 'AWS Certified Solutions Architect Study Guide SAA-C03',
subtitle: 'The 30-Day Cram Sheet & High-Performance Practice Exam Bank',
type: 'study_guide',
price: 14.99,
date: new Date().toLocaleDateString()
}
];
fs.writeFileSync(DB_FILE, JSON.stringify(defaultCatalog, null, 2));
return defaultCatalog;
}
return JSON.parse(fs.readFileSync(DB_FILE, 'utf8'));
} catch (err) {
return [];
}
};
const writeDb = (data) => {
fs.writeFileSync(DB_FILE, JSON.stringify(data, null, 2));
};
// Initialize Hugging Face Inference (used ONLY for SDXL image generation)
const hf = new HfInference(process.env.HF_API_KEY || '');
// ----------------------------------------------------
// GROQ LLAMA 3.3 70B β€” Primary Text Generation Engine
// ----------------------------------------------------
const groqChat = async (prompt, maxTokens = 1200) => {
if (!process.env.GROQ_API_KEY) throw new Error('GROQ_API_KEY not configured in .env');
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'llama-3.3-70b-versatile',
messages: [{ role: 'user', content: prompt }],
max_tokens: maxTokens,
temperature: 0.8
})
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message || 'Groq API error');
return data.choices[0].message.content;
};
const groqJsonChat = async (prompt, maxTokens = 3800) => {
if (!process.env.GROQ_API_KEY) throw new Error('GROQ_API_KEY not configured in .env');
const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'llama-3.3-70b-versatile',
messages: [
{
role: 'system',
content: 'You are an elite expert digital product creator and author. You must respond ONLY with a valid JSON object matching the requested schema.'
},
{ role: 'user', content: prompt }
],
response_format: { type: 'json_object' },
max_tokens: maxTokens,
temperature: 0.8
})
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message || 'Groq API error');
return JSON.parse(data.choices[0].message.content);
};
const humanizeBookContent = async (content) => {
if (!content) return content;
console.log(`[HUMANIZER] Initiating 100% human-like refinement pass...`);
const prompt = `You are an elite expert developmental editor and premium humanizer.
Your absolute goal is to make this book/guide draft read exactly as if it was written by an incredibly intelligent, expert human author.
CRITICAL DIRECTIVES:
1. PRESERVE THE LAYOUT EXACTLY: Do not alter any Markdown headers (#, ##, ###), Table of Contents, code blocks, or structured chapter key point summaries (βœ“). Keep the exact same format.
2. 100% HUMANIZED STYLE: Rewrite the content to flow naturally, using conversational yet highly authoritative, warm, and precise easy-to-read English. Prefer short active sentences and a direct tone.
3. REMOVE ALL AI TRADEMARK FLUFF: Eliminate ALL AI-like introductory filler, generic summaries, and hallmark transition phrases (e.g. "Let's dive in", "first and foremost", "it is important to remember", "a testament to", "unlock your potential", "delve into", "in today's digital era").
4. ZERO REPETITION: Actively prune any redundant paragraphs, circular explanations, or repetitive AI patterns.
5. MAXIMIZE PRACTICAL VALUE: Make all descriptions direct, highly practical, and pack them with clear real-world examples.
Do NOT summarize, truncate, or cut the length. Ensure the resulting book remains fully detailed, comprehensive, and complete.
Here is the draft book content to humanize:
---
${content}
---
Respond with ONLY the polished, 100% humanized markdown content. Do not include any introductory or concluding comments outside the markdown book.`;
return await groqChat(prompt, 6500);
};
const parseResponseFields = (text, defaultNiche = 'Digital Guide', defaultCta = 'Get instant access') => {
const fields = {
title: '',
subtitle: '',
description: '',
tags: '',
cta: '',
thumbnailPrompt: '',
productContent: ''
};
if (!text) return fields;
const keys = [
{ key: 'title', name: 'TITLE', next: 'SUBTITLE' },
{ key: 'subtitle', name: 'SUBTITLE', next: 'DESCRIPTION' },
{ key: 'description', name: 'DESCRIPTION', next: 'TAGS' },
{ key: 'tags', name: 'TAGS', next: 'CTA' },
{ key: 'cta', name: 'CTA', next: 'THUMBNAIL_PROMPT' },
{ key: 'thumbnailPrompt', name: 'THUMBNAIL_PROMPT', next: 'PRODUCT_CONTENT' },
{ key: 'productContent', name: 'PRODUCT_CONTENT', next: 'ZZEND' }
];
// 1. Try strict matching with brackets first
let hasBrackets = false;
for (const k of keys) {
const r = new RegExp(`\\[${k.name}\\]\\n?([\\s\\S]*?)(?=\\[${k.next}\\]|$)`, 'i');
const m = text.match(r);
if (m && m[1].trim()) {
fields[k.key] = m[1].trim();
hasBrackets = true;
}
}
if (hasBrackets && fields.title) {
return fields;
}
console.log("[PARSER] Strict bracket match failed/incomplete. Running smart lexical segmenter...");
// 2. Lexical Segmenter: find sections using common labels
const getSection = (startRegex, endRegex) => {
const startMatch = text.match(startRegex);
if (!startMatch) return '';
const startIndex = startMatch.index + startMatch[0].length;
let endIndex = text.length;
if (endRegex) {
const endMatch = text.match(endRegex);
if (endMatch && endMatch.index > startIndex) {
endIndex = endMatch.index;
}
}
return text.substring(startIndex, endIndex).trim();
};
const descLabel = /(?:^|\n)(?:description|\[description\]|\*\*description\*\*)\s*(?::|-|\n)\s*/i;
const tagsLabel = /(?:^|\n)(?:tags|\[tags\]|\*\*tags\*\*)\s*(?::|-|\n)\s*/i;
const thumbnailLabel = /(?:^|\n)(?:thumbnail\s*(?:prompt)?|\[thumbnail_prompt\]|\*\*thumbnail_prompt\*\*)\s*(?::|-|\n)\s*/i;
const contentLabel = /(?:^|\n)(?:product\s*(?:content)?|\[product_content\]|\*\*product_content\*\*)\s*(?::|-|\n)\s*/i;
const descText = getSection(descLabel, tagsLabel);
const tagsText = getSection(tagsLabel, thumbnailLabel);
const thumbText = getSection(thumbnailLabel, contentLabel);
const contentText = getSection(contentLabel, null);
fields.description = descText || fields.description;
fields.tags = tagsText || fields.tags;
fields.thumbnailPrompt = thumbText || fields.thumbnailPrompt;
fields.productContent = contentText || fields.productContent;
// Find TITLE and SUBTITLE (text before the description label)
const preDesc = text.split(descLabel)[0].trim();
const preDescLines = preDesc.split('\n').map(l => l.trim()).filter(Boolean);
if (preDescLines.length > 0) {
fields.title = preDescLines[0].replace(/^(title:?|\[title\]|\*\*title\*\*)\s*/i, '');
if (preDescLines.length > 1) {
fields.subtitle = preDescLines.slice(1).join(' ').replace(/^(subtitle:?|\[subtitle\]|\*\*subtitle\*\*)\s*/i, '');
}
}
// Find CTA (usually the lines between tags and thumbnailPrompt that are not tags)
const preThumb = text.split(thumbnailLabel)[0].trim();
const preThumbLines = preThumb.split('\n').map(l => l.trim()).filter(Boolean);
if (preThumbLines.length > 0) {
for (let i = preThumbLines.length - 1; i >= 0; i--) {
const line = preThumbLines[i];
if (!line.toLowerCase().startsWith('tags:') && !line.toLowerCase().startsWith('description:') && line.split(',').length < 3 && line.length > 10) {
fields.cta = line.replace(/^(cta:?|\[cta\]|\*\*cta\*\*)\s*/i, '');
break;
}
}
}
// Clean up fields if they are still empty
if (!fields.title) fields.title = defaultNiche;
if (!fields.cta) fields.cta = defaultCta;
if (fields.tags) {
fields.tags = fields.tags.split('\n')[0].replace(/^(tags:?|\[tags\]|\*\*tags\*\*)\s*/i, '').trim();
}
return fields;
};
// Health Check Endpoint
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
groqConfigured: !!process.env.GROQ_API_KEY,
hfConfigured: !!process.env.HF_API_KEY,
dbType: isSupabaseConnected ? 'supabase' : 'local_json',
dbConnected: true
});
});
// ----------------------------------------------------
// DATABASE CATALOG ENDPOINTS ("AI DB")
// ----------------------------------------------------
app.get('/api/catalog', async (req, res) => {
if (isSupabaseConnected) {
try {
const { data, error } = await supabase
.from('catalog')
.select('*')
.order('id', { ascending: false });
if (error) throw error;
return res.json(data || []);
} catch (err) {
console.error('Error fetching from Supabase:', err.message);
// Fallback
}
}
// JSON Fallback
const catalog = readDb();
res.json(catalog);
});
app.post('/api/catalog', async (req, res) => {
const {
title,
subtitle,
type,
price,
platform,
thumbnail_url,
tags,
sales_copy,
status,
published_url
} = req.body;
if (!title) {
return res.status(400).json({ error: 'Book title is required' });
}
const finalPlatform = platform || 'kdp';
const finalStatus = status || 'draft';
const finalPrice = price || 9.99;
if (isSupabaseConnected) {
try {
const { data, error } = await supabase
.from('catalog')
.insert([{
title,
subtitle: subtitle || '',
type: type || 'other',
price: finalPrice,
platform: finalPlatform,
thumbnail_url: thumbnail_url || '',
tags: tags || '',
sales_copy: sales_copy || '',
status: finalStatus,
published_url: published_url || ''
}])
.select();
if (error) throw error;
if (data && data.length > 0) {
return res.status(201).json(data[0]);
}
} catch (err) {
console.error('Error saving to Supabase:', err.message);
// Fallback
}
}
// JSON Fallback
const catalog = readDb();
const newBook = {
id: Date.now().toString(),
title,
subtitle: subtitle || '',
type: type || 'other',
price: finalPrice,
platform: finalPlatform,
thumbnail_url: thumbnail_url || '',
tags: tags || '',
sales_copy: sales_copy || '',
status: finalStatus,
published_url: published_url || '',
date: new Date().toLocaleDateString()
};
catalog.unshift(newBook);
writeDb(catalog);
res.status(201).json(newBook);
});
app.delete('/api/catalog/:id', async (req, res) => {
const { id } = req.params;
if (isSupabaseConnected) {
try {
const { error } = await supabase
.from('catalog')
.delete()
.eq('id', id);
if (error) throw error;
return res.json({ success: true });
} catch (err) {
console.error('Error deleting from Supabase:', err.message);
// Fallback
}
}
// JSON Fallback
let catalog = readDb();
const originalLength = catalog.length;
catalog = catalog.filter(book => book.id !== id);
if (catalog.length === originalLength) {
return res.status(404).json({ error: 'Book not found' });
}
writeDb(catalog);
res.json({ success: true });
});
// 1. TEXT GENERATION API (Uses Groq Llama 3.3 70B)
app.post('/api/generate-text', async (req, res) => {
const { prompt, maxTokens = 800 } = req.body;
if (!process.env.GROQ_API_KEY) {
return res.status(401).json({ error: 'Please set your GROQ_API_KEY in the server/.env file' });
}
try {
const text = await groqChat(prompt, maxTokens);
res.json({ text });
} catch (error) {
console.error('Groq Text Error:', error);
res.status(500).json({ error: error.message });
}
});
// 2. FREE COVER ART GENERATION API (Uses Stable Diffusion XL)
app.post('/api/generate-cover', async (req, res) => {
const { prompt, title } = req.body;
if (!process.env.HF_API_KEY) {
return res.status(401).json({ error: 'Please set your HF_API_KEY in the server/.env file' });
}
const models = [
'black-forest-labs/FLUX.1-schnell',
'stabilityai/stable-diffusion-xl-base-1.0',
'stabilityai/stable-diffusion-3.5-medium'
];
let responseBlob = null;
let lastError = null;
for (const model of models) {
try {
console.log(`[HF] Attempting cover generation with model: ${model}...`);
responseBlob = await hf.textToImage({
model: model,
inputs: prompt,
parameters: model.includes('flux') ? {} : {
negative_prompt: 'blurry, low quality, distorted, text, watermark'
}
});
console.log(`[HF] Successfully generated cover using model: ${model}`);
break;
} catch (err) {
console.warn(`[HF] Model ${model} failed: ${err.message}`);
lastError = err;
}
}
const safeTitle = (title || 'cover').replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
const baseUrl = getBaseUrl(req);
if (responseBlob) {
try {
const arrayBuffer = await responseBlob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const base64Image = buffer.toString('base64');
const coverFilename = `${safeTitle}_${Date.now()}.jpg`;
const coverPath = path.join(coversDir, coverFilename);
fs.writeFileSync(coverPath, buffer);
const coverUrl = `${baseUrl}/static/covers/${coverFilename}`;
return res.json({
imageUrl: `data:image/jpeg;base64,${base64Image}`,
coverUrl,
coverFilename
});
} catch (bufErr) {
console.error('Error handling HF response buffer:', bufErr);
lastError = bufErr;
}
}
// ----------------------------------------------------
// RESILIENT FALLBACK: Generate Premium Vector SVG Cover
// ----------------------------------------------------
console.log(`[HF] All models failed. Generating premium vector SVG fallback cover...`);
const svgContent = `
<svg width="600" height="900" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8b5cf6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="600" height="900" fill="url(#grad)" />
<rect x="30" y="30" width="540" height="840" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="4" rx="10" />
<!-- Brand Header -->
<rect x="250" y="80" width="100" height="24" fill="rgba(255,255,255,0.1)" rx="12" />
<text x="300" y="96" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="10" font-weight="bold" fill="rgba(255,255,255,0.8)" text-anchor="middle" letter-spacing="2">BOOKFORGE</text>
<!-- Dynamic Title -->
<foreignObject x="60" y="240" width="480" height="350">
<div xmlns="http://www.w3.org/1999/xhtml" style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:42px; font-weight:800; color:#ffffff; text-align:center; line-height:1.2; text-shadow:0 4px 12px rgba(0,0,0,0.3);">
${(title || 'BOOKFORGE DIGITAL').toUpperCase()}
</div>
</foreignObject>
<!-- Footer Sub-branding -->
<line x1="200" y1="650" x2="400" y2="650" stroke="rgba(255,255,255,0.3)" stroke-width="2" />
<text x="300" y="690" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle" letter-spacing="4">PREMIUM EDITION</text>
<text x="300" y="720" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="11" fill="rgba(255,255,255,0.6)" text-anchor="middle">COMPLETE STUDY GUIDE &amp; PLANNER</text>
</svg>
`;
const svgBuffer = Buffer.from(svgContent);
const base64Svg = svgBuffer.toString('base64');
const coverFilename = `${safeTitle}_${Date.now()}.svg`;
const coverPath = path.join(coversDir, coverFilename);
fs.writeFileSync(coverPath, svgBuffer);
const coverUrl = `${baseUrl}/static/covers/${coverFilename}`;
res.json({
imageUrl: `data:image/svg+xml;base64,${base64Svg}`,
coverUrl,
coverFilename
});
});
// 3. MULTI-PLATFORM DIGITAL PRODUCT GENERATOR
const PLATFORM_STRATEGIES = {
gumroad: { name: 'Gumroad', emoji: '🟠', priceMin: 7, priceMax: 27, style: 'personal story + results proof', cta: 'Pay what you want or get instant access' },
etsy: { name: 'Etsy', emoji: '🟑', priceMin: 2, priceMax: 8, style: 'INSTANT DOWNLOAD urgency + printable keywords', cta: 'Instant Digital Download β€” Print at Home' },
kdp: { name: 'Amazon KDP', emoji: 'πŸ”΅', priceMin: 9, priceMax: 15, style: 'SEO keyword-first title', cta: 'Available on Amazon KDP β€” Kindle and Paperback' },
kofi: { name: 'Ko-fi', emoji: 'β˜•', priceMin: 3, priceMax: 10, style: 'community + exclusivity + support', cta: 'Support the creator and get instant access' },
payhip: { name: 'Payhip', emoji: 'πŸ’œ', priceMin: 5, priceMax: 20, style: 'value stack + benefit headline', cta: 'Secure Checkout β€” Instant Delivery' },
creative: { name: 'Creative Market', emoji: '🎨', priceMin: 15, priceMax: 49, style: 'professional premium quality fonts/graphics', cta: 'Professional Grade β€” Commercial License Included' },
teachable:{ name: 'Teachable', emoji: 'πŸŽ“', priceMin: 19, priceMax: 97, style: 'transformation promise complete guide', cta: 'Enroll Now β€” Lifetime Access' },
envato: { name: 'Envato', emoji: '🟒', priceMin: 12, priceMax: 45, style: 'premium theme/template professional', cta: 'Extended License Available' }
};
// Helper to get base URL dynamically
const getBaseUrl = (req) => {
const host = req.headers['x-forwarded-host'] || req.headers.host || 'localhost:5000';
const protocol = req.headers['x-forwarded-proto'] || 'http';
const isHF = host.includes('hf.space');
return `${isHF ? 'https' : protocol}://${host}`;
};
app.post('/api/generate-product', async (req, res) => {
const { platform = 'gumroad', niche } = req.body;
if (!niche) return res.status(400).json({ error: 'Niche is required' });
if (!process.env.GROQ_API_KEY) return res.status(401).json({ error: 'GROQ_API_KEY not configured' });
const strategy = PLATFORM_STRATEGIES[platform] || PLATFORM_STRATEGIES.gumroad;
const optimalPrice = (Math.random() * (strategy.priceMax - strategy.priceMin) + strategy.priceMin).toFixed(2);
const prompt = `Create a complete, high-converting digital book package for this niche: "${niche}", optimized for ${strategy.name} using the ${strategy.style} approach.
CRITICAL ORIGINALITY & COPYRIGHT DIRECTIVES:
- Content must be 100% original, creative, and written from scratch. Do NOT copy, paraphrase, or borrow from any copyrighted books, publications, or trademarked products.
- Focus on high-value, highly practical content (e.g., comprehensive study guides, structured planners, templates, step-by-step how-to guides, and engaging hobby/educational materials) rather than low-quality generic text.
- If this guide contains technical, factual, or educational information, ensure all facts are reviewed, verified, and include realistic, helpful examples and citations where appropriate.
- COMPLIANCE & DISCLOSURE: Disclose AI assistance transparently as required by platform policies (e.g. KDP/Etsy). Add a small, elegant "Research & Editorial Note: Designed with the assistance of advanced generative tools and verified by human subject matter experts for accuracy" to the product introduction.
Your response must be a single, valid JSON object matching this schema EXACTLY:
{
"title": "A viral, specific title optimized for self-publishing (e.g. '90-Day Indian Weight Loss Plan' instead of generic 'Weight Loss'). Max 80 characters.",
"subtitle": "A benefit-driven subtitle with an emotional hook. Max 150 characters.",
"description": "A 150-word high-converting product description with emotional hooks and bullet benefits.",
"tags": "An array of exactly 5 comma-separated SEO tags for ${strategy.name}.",
"cta": "One powerful call-to-action sentence (max 20 words).",
"thumbnailPrompt": "A detailed Stable Diffusion prompt for a stunning professional product cover image. Include style, colors, composition. Keep under 80 words.",
"productContent": "The actual complete high-quality digital book. Use this EXACT structure:
- Detailed Table of Contents (listing all 5 chapters)
- Chapter 1 - Introduction (Comprehensive introductory text in easy-to-read English, short paragraphs, real-world examples, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 2 - Basics (Solid explanation of core concepts, foundational steps, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 3 - Advanced Techniques (High-level professional strategies, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 4 - Case Studies (Actionable case studies, templates, or checklists, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 5 - Conclusion (Final takeaways, future roadmaps, ending with a 'Key Points' summary with checkmarks 'βœ“')"
}
Ensure the "productContent" is extremely comprehensive, highly valuable, and direct, avoiding generic AI repetitions or empty introductory filler phrases. Let it be a complete, ready-to-sell book of at least 3000-4000 words. Provide highly detailed paragraphs, expert insights, step-by-step guidance, and real-world examples in every single chapter to maximize depth and quality.`;
try {
const data = await groqJsonChat(prompt, 6000);
const title = data.title || niche;
const subtitle = data.subtitle || '';
const description = data.description || '';
const tags = Array.isArray(data.tags) ? data.tags.join(', ') : (data.tags || '');
const cta = data.cta || strategy.cta;
const thumbnailPrompt = data.thumbnailPrompt || '';
let productContent = data.productContent || 'Sample digital product guide content...';
// Humanizer Pass to make it 100% human-like
try {
console.log(`[HUMANIZER] Running humanization pass...`);
productContent = await humanizeBookContent(productContent);
console.log(`[HUMANIZER] Book successfully humanized!`);
} catch (hErr) {
console.error('Humanizer Pass failed, keeping original:', hErr);
}
// Save product content to file
const safeTitle = (title || niche).replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
const productFilename = `${safeTitle}_${Date.now()}.md`;
const productPath = path.join(productsDir, productFilename);
fs.writeFileSync(productPath, productContent);
const baseUrl = getBaseUrl(req);
const productUrl = `${baseUrl}/static/products/${productFilename}`;
res.json({
platform,
platformName: strategy.name,
platformEmoji: strategy.emoji,
niche,
title,
subtitle,
description,
tags,
cta,
thumbnailPrompt,
productContent,
productFilename,
productUrl,
price: parseFloat(optimalPrice),
priceLabel: `$${optimalPrice}`,
});
} catch (err) {
console.error('Generate Product Error:', err);
res.status(500).json({ error: err.message });
}
});
// ----------------------------------------------------
// AI AGENT AUTOPILOT ENGINE ("AUTO-PRODUCT PIPELINE")
// ----------------------------------------------------
// ----------------------------------------------------
// ZIP PACKAGE BUILDER
// Creates a downloadable ZIP with: book.md, cover.jpg, listing.txt
// ----------------------------------------------------
const compile120PageHtml = (title, subtitle, tags, productContent, theme = 'classic') => {
const text = productContent || '';
const BOOK_THEMES = {
classic: {
primary: '#000000',
border: '#000000',
bgLight: '#ffffff',
dots: 'rgba(0,0,0,0.3)',
gradient: '#000000'
},
cherry: {
primary: '#db2777',
border: '#db2777',
bgLight: '#fdf2f8',
dots: 'rgba(219,39,119,0.35)',
gradient: 'linear-gradient(135deg, #db2777, #7c3aed)'
},
emerald: {
primary: '#059669',
border: '#059669',
bgLight: '#ecfdf5',
dots: 'rgba(5,150,105,0.35)',
gradient: 'linear-gradient(135deg, #059669, #0891b2)'
},
ocean: {
primary: '#2563eb',
border: '#2563eb',
bgLight: '#eff6ff',
dots: 'rgba(37,99,235,0.35)',
gradient: 'linear-gradient(135deg, #2563eb, #0d9488)'
},
sunset: {
primary: '#d97706',
border: '#d97706',
bgLight: '#fffbeb',
dots: 'rgba(217,119,6,0.35)',
gradient: 'linear-gradient(135deg, #d97706, #dc2626)'
}
};
const themeCfg = BOOK_THEMES[theme] || BOOK_THEMES.classic;
// Bulletproof Chapter Slicer bypassing the TOC
const chapters = [];
for (let idx = 1; idx <= 5; idx++) {
const searchStr = `Chapter ${idx}`;
const firstIdx = text.indexOf(searchStr);
let startIndex = -1;
if (firstIdx !== -1) {
// Look for the second occurrence to bypass TOC
startIndex = text.indexOf(searchStr, firstIdx + searchStr.length);
if (startIndex === -1) {
startIndex = firstIdx; // fallback to first
}
}
if (startIndex !== -1) {
let endIndex = text.length;
if (idx < 5) {
const nextSearchStr = `Chapter ${idx + 1}`;
const nextFirstIdx = text.indexOf(nextSearchStr);
let nextStartIndex = -1;
if (nextFirstIdx !== -1) {
nextStartIndex = text.indexOf(nextSearchStr, nextFirstIdx + nextSearchStr.length);
if (nextStartIndex === -1) {
nextStartIndex = nextFirstIdx;
}
}
if (nextStartIndex !== -1) {
endIndex = nextStartIndex;
}
}
let chapterText = text.substring(startIndex, endIndex).trim();
// Remove trailing Markdown header symbols if present
if (chapterText.endsWith('#')) {
chapterText = chapterText.substring(0, chapterText.lastIndexOf('#')).trim();
}
chapters.push(chapterText);
} else {
chapters.push(`Chapter ${idx}\n\nContent for chapter ${idx} is detailed here.`);
}
}
// Dynamic 120-Page Assembler
let pagesHtml = '';
// Page 1: Title Page
pagesHtml += `
<div class="page left-page cover-page">
<div class="header-brand">BOOKFORGE DIGITAL HUBS</div>
<div class="cover-interior">
<h1 class="cover-title">${title}</h1>
<div class="cover-divider"></div>
<p class="cover-subtitle">${subtitle || ''}</p>
<div class="cover-tag">COMPLETE GUIDE & PREMIUM BINDER</div>
</div>
<div class="cover-footer">BOOKFORGE PREMIUM EDITION</div>
</div>
`;
// Page 2: Copyright & Disclosures Page
pagesHtml += `
<div class="page right-page copyright-page">
<div class="header-brand">BOOKFORGE BINDERY & REGISTRATION</div>
<div class="interior-content">
<h3>COPYRIGHT & TRADEMARKS</h3>
<p>&copy; ${new Date().getFullYear()} BookForge Digital Publishing. All rights reserved.</p>
<p>No part of this publication may be reproduced, distributed, or transmitted in any form or by any means, including photocopying, recording, or other electronic or mechanical methods, without the prior written permission of the publisher, except in the case of brief quotations embodied in critical reviews.</p>
<h3 style="margin-top:40px;">EDITORIAL & RESEARCH DISCLOSURE</h3>
<p><strong>Research & Editorial Note:</strong> Designed with the assistance of advanced generative tools and verified by human subject matter experts for accuracy, original insights, and educational excellence. Checked strictly against copyright frameworks for complete compliance and original value creation.</p>
<h3 style="margin-top:40px;">PUBLISHING METADATA</h3>
<p><strong>Trade Size:</strong> 6" x 9" inches (bleed enabled)<br>
<strong>Spine Width Offset:</strong> 0.2704 inches (120 cream interior pages)<br>
<strong>Niche Strategy:</strong> ${tags || 'General self-publishing'}</p>
</div>
<div class="page-number">Page 2</div>
</div>
`;
// Page 3: Table of Contents
pagesHtml += `
<div class="page left-page toc-page">
<div class="header-brand">CONTENTS INDEX</div>
<div class="interior-content">
<h2>TABLE OF CONTENTS</h2>
<div class="cover-divider" style="width:30px; margin: 5px auto 30px auto;"></div>
<div class="toc-list">
<div class="toc-item"><span>PREFACE & AUTHOR NOTE</span><span class="dots">.......................................................................</span><span>PAGE 4</span></div>
<div class="toc-item"><span>CHAPTER 1 β€” INTRODUCTION & OVERVIEW</span><span class="dots">.......................................................................</span><span>PAGE 5</span></div>
<div class="toc-item"><span>CHAPTER 1 GOAL WORKBOOKS & JOURNALS</span><span class="dots">.......................................................................</span><span>PAGE 7</span></div>
<div class="toc-item"><span>CHAPTER 2 β€” FOUNDATIONS & CORE TECHNIQUES</span><span class="dots">.......................................................................</span><span>PAGE 29</span></div>
<div class="toc-item"><span>CHAPTER 2 ROUTINE PLANNERS & RECORDS</span><span class="dots">.......................................................................</span><span>PAGE 31</span></div>
<div class="toc-item"><span>CHAPTER 3 β€” ADVANCED DEEP-DIVE STRATEGIES</span><span class="dots">.......................................................................</span><span>PAGE 53</span></div>
<div class="toc-item"><span>CHAPTER 3 STRATEGY WORKBOOKS</span><span class="dots">.......................................................................</span><span>PAGE 55</span></div>
<div class="toc-item"><span>CHAPTER 4 β€” DETAILED CASE STUDY REVIEW</span><span class="dots">.......................................................................</span><span>PAGE 77</span></div>
<div class="toc-item"><span>CHAPTER 4 ACTION PLAN CHECKS</span><span class="dots">.......................................................................</span><span>PAGE 79</span></div>
<div class="toc-item"><span>CHAPTER 5 β€” CONCLUSION & ROADMAPS</span><span class="dots">.......................................................................</span><span>PAGE 101</span></div>
<div class="toc-item"><span>CHAPTER 5 REFLECTION WORKBOOKS</span><span class="dots">.......................................................................</span><span>PAGE 103</span></div>
</div>
</div>
<div class="page-number">Page 3</div>
</div>
`;
// Page 4: Preface / Intro letter
pagesHtml += `
<div class="page right-page preface-page">
<div class="header-brand">AUTHOR PREFACE</div>
<div class="interior-content">
<h2>AUTHOR'S INTRODUCTORY LETTER</h2>
<div class="cover-divider" style="width:30px; margin: 5px auto 20px auto;"></div>
<p>Welcome to this premium combined edition. Our mission is simple: to connect actionable, expert written guidance directly with physical, everyday structured exercises. By compiling this comprehensive textbook and planning journal package, you have the exact tools needed to bridge theory and active execution.</p>
<p>As you progress through each chapter, read the developmental insights generated to lay the conceptual groundwork. Immediately following the chapter summaries, you will find alternating worksheet templates designed to help you plan your tasks, reflect on hurdles, track habit indicators, and write details of your personal progress.</p>
<p>Set aside time daily, track your goals strictly, and let's forge your path to success page by page.</p>
<p style="margin-top: 40px; font-style: italic; font-weight: bold; text-align: right;">β€” The Editorial Team, BookForge Digital</p>
</div>
<div class="page-number">Page 4</div>
</div>
`;
// Chapters Loop
let globalPageNum = 5;
for (let cIdx = 0; cIdx < 5; cIdx++) {
const isLeft = globalPageNum % 2 !== 0;
const chClass = isLeft ? 'left-page' : 'right-page';
const rawText = chapters[cIdx] || '';
// Clean and format text
const cleanHeading = rawText.split('\n')[0].replace(/#+/g, '').replace(/:/g, '').trim().toUpperCase();
const bodyText = rawText.split('\n').slice(1).join('\n')
.replace(/\n/g, '<br>')
.replace(/βœ“ (.*?)(?=<br>)/g, '<li>βœ“ $1</li>');
// 1. Chapter Title Page (1 page)
pagesHtml += `
<div class="page ${chClass} chapter-cover">
<div class="header-brand">CHAPTER DIVISION ${cIdx + 1}</div>
<div class="chapter-cover-interior">
<span class="chapter-number">CHAPTER 0${cIdx + 1}</span>
<div class="cover-divider"></div>
<h2>${cleanHeading}</h2>
<p style="font-size: 11px; text-transform:uppercase; color:#333; letter-spacing:1px; margin-top:20px;">Detailed Written Guide & Workbook Interior</p>
</div>
<div class="page-number">Page ${globalPageNum}</div>
</div>
`;
globalPageNum++;
// 2. Chapter Written Content Page (1 page)
const isLeft2 = globalPageNum % 2 !== 0;
const chClass2 = isLeft2 ? 'left-page' : 'right-page';
pagesHtml += `
<div class="page ${chClass2} chapter-content">
<div class="header-brand">${cleanHeading}</div>
<div class="chapter-text-interior">
<h3>DEVELOPMENTAL MANUAL OUTLINE</h3>
<p>${bodyText}</p>
</div>
<div class="page-number">Page ${globalPageNum}</div>
</div>
`;
globalPageNum++;
// 3. Alternate Workbook Sheets (22 pages for Chapters 1-4, 18 pages for Chapter 5)
const numSheets = (cIdx === 4) ? 18 : 22;
for (let sheetIdx = 0; sheetIdx < numSheets; sheetIdx++) {
const isLeftSheet = globalPageNum % 2 !== 0;
const chClassSheet = isLeftSheet ? 'left-page' : 'right-page';
let sheetContentHtml = '';
if (cIdx === 0) {
if (sheetIdx % 2 === 0) {
sheetContentHtml = `
<h2>DAILY FOCUS TRACKER</h2>
<div class="divider"></div>
<div class="box">
<span class="label">MY TOP 3 MOTIVATORS FOR TODAY</span>
<div class="line"></div>
<div class="line"></div>
</div>
<div class="box" style="height:120px;">
<span class="label">CREATIVE SKETCH / NOTES AREA</span>
<div class="doodle-placeholder"></div>
</div>
<div class="box">
<span class="label">HIGH-PRIORITY TASKS</span>
<div class="checkbox-row"><div class="checkbox"></div><div class="line" style="width:75%"></div></div>
<div class="checkbox-row"><div class="checkbox"></div><div class="line" style="width:75%"></div></div>
</div>
`;
} else {
sheetContentHtml = `
<h2>INSIGHT & NOTES</h2>
<div class="divider"></div>
<div class="dot-grid">
${'<div class="dot">.</div>'.repeat(80)}
</div>
`;
}
} else if (cIdx === 1) {
if (sheetIdx % 2 === 0) {
sheetContentHtml = `
<h2>CORE HABIT BUILDER</h2>
<div class="divider"></div>
<div class="box">
<span class="label">HABITS TO BUILD</span>
<div class="habit-row"><div class="line" style="width:30%"></div><div class="circles">βšͺβšͺβšͺβšͺβšͺβšͺβšͺ</div></div>
<div class="habit-row"><div class="line" style="width:30%"></div><div class="circles">βšͺβšͺβšͺβšͺβšͺβšͺβšͺ</div></div>
<div class="habit-row"><div class="line" style="width:30%"></div><div class="circles">βšͺβšͺβšͺβšͺβšͺβšͺβšͺ</div></div>
</div>
<div class="box" style="height:100px; margin-top:20px;">
<span class="label">HABIT REFLECTION COMMENTS</span>
<div class="line"></div>
<div class="line"></div>
</div>
`;
} else {
sheetContentHtml = `
<h2>CREATIVE BRAINSTORM LOG</h2>
<div class="divider"></div>
<div class="box" style="height:80px; margin-bottom:15px;">
<span class="label">CENTRAL CONCEPT TOPIC</span>
<div class="line"></div>
</div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
`;
}
} else if (cIdx === 2) {
if (sheetIdx % 2 === 0) {
sheetContentHtml = `
<h2>PRIORITY STRATEGY MATRIX</h2>
<div class="divider"></div>
<div class="matrix-grid">
<div class="matrix-box">
<div class="label" style="border:none;">πŸ”₯ 1. URGENT & IMPORTANT</div>
<div class="line"></div>
<div class="line"></div>
</div>
<div class="matrix-box">
<div class="label" style="border:none;">πŸ“… 2. NOT URGENT & IMPORTANT</div>
<div class="line"></div>
<div class="line"></div>
</div>
<div class="matrix-box">
<div class="label" style="border:none;">⚑ 3. URGENT & NOT IMPORTANT</div>
<div class="line"></div>
<div class="line"></div>
</div>
<div class="matrix-box">
<div class="label" style="border:none;">πŸ’€ 4. ELIMINATE / DELEGATE</div>
<div class="line"></div>
<div class="line"></div>
</div>
</div>
`;
} else {
sheetContentHtml = `
<h2>INSIGHT & NOTES</h2>
<div class="divider"></div>
<div class="dot-grid">
${'<div class="dot">.</div>'.repeat(80)}
</div>
`;
}
} else if (cIdx === 3) {
if (sheetIdx % 2 === 0) {
sheetContentHtml = `
<h2>CASE STUDY REFLECTION LOG</h2>
<div class="divider"></div>
<table class="budget-table">
<thead><tr><th>ACTION STEP REQUIRED</th><th>TIMELINE</th></tr></thead>
<tbody>
<tr><td><div class="line" style="width:90%"></div></td><td>___ Days</td></tr>
<tr><td><div class="line" style="width:90%"></div></td><td>___ Days</td></tr>
<tr><td><div class="line" style="width:90%"></div></td><td>___ Days</td></tr>
</tbody>
</table>
<div class="box" style="height:90px; margin-top:20px;">
<span class="label">KEY PERFORMANCE METRIC TO DEFINE</span>
<div class="line"></div>
</div>
`;
} else {
sheetContentHtml = `
<h2>CREATIVE BRAINSTORM LOG</h2>
<div class="divider"></div>
<div class="box" style="height:80px; margin-bottom:15px;">
<span class="label">CENTRAL CONCEPT TOPIC</span>
<div class="line"></div>
</div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
`;
}
} else {
if (sheetIdx % 2 === 0) {
sheetContentHtml = `
<h2>90-DAY PROGRESS SUMMARY</h2>
<div class="divider"></div>
<div class="box">
<span class="label">MILESTONES COMPLETED & WINS</span>
<div class="checkbox-row"><div class="checkbox"></div><div class="line" style="width:75%"></div></div>
<div class="checkbox-row"><div class="checkbox"></div><div class="line" style="width:75%"></div></div>
</div>
<div class="box" style="height:120px; margin-top:15px;">
<span class="label">FUTURE PERSPECTIVE ROADMAP</span>
<div class="line"></div>
<div class="line"></div>
</div>
`;
} else {
sheetContentHtml = `
<h2>INSIGHT & NOTES</h2>
<div class="divider"></div>
<div class="dot-grid">
${'<div class="dot">.</div>'.repeat(80)}
</div>
`;
}
}
pagesHtml += `
<div class="page ${chClassSheet}">
<div class="header-brand">BOOKFORGE DIGITAL GOAL PLANNERS</div>
<div class="page-interior">
${sheetContentHtml}
</div>
<div class="page-number">Page ${globalPageNum}</div>
</div>
`;
globalPageNum++;
}
}
return `
<html>
<head>
<title>${title} - 120 Pages</title>
<style>
@media print {
body { margin: 0; padding: 0; background: #fff; }
.page { page-break-after: always; display: flex !important; }
@page { size: 6in 9in; margin: 0; }
}
* {
box-sizing: border-box;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #000;
margin: 0;
padding: 0;
background: #fff;
}
.page {
background: #fff;
width: 6in;
height: 9in;
margin: 0 auto;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
border: none;
}
/* Gutter Margins Alternating (Perfect Spine Binding) */
.left-page {
padding: 0.8in 0.75in 0.8in 0.4in;
}
.right-page {
padding: 0.8in 0.4in 0.8in 0.75in;
}
.header-brand {
font-size: 8px;
color: ${themeCfg.primary};
letter-spacing: 2px;
text-align: center;
border-bottom: 2px solid ${themeCfg.border};
padding-bottom: 6px;
text-transform: uppercase;
font-weight: 700;
}
.page-interior {
flex-grow: 1;
padding: 20px 0;
display: flex;
flex-direction: column;
gap: 15px;
}
.interior-content {
padding: 40px 10px 0 10px;
}
/* Cover page styling */
.cover-page {
justify-content: space-between;
padding: 1.2in 0.6in;
text-align: center;
border: 3px solid ${themeCfg.border};
}
.cover-interior {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1.5in;
}
.cover-title {
font-family: 'Georgia', serif;
font-size: 26px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 1px;
line-height: 1.3;
margin: 0 0 15px 0;
color: ${themeCfg.primary};
}
.cover-divider {
width: 80px;
height: 4px;
background: ${themeCfg.gradient};
margin: 15px 0;
}
.cover-subtitle {
font-family: 'Georgia', serif;
font-size: 14px;
color: #111;
font-style: italic;
line-height: 1.5;
margin: 15px 0;
max-width: 4.5in;
}
.cover-tag {
font-size: 10px;
font-weight: 900;
border: 2px solid ${themeCfg.border};
border-radius: 4px;
padding: 6px 12px;
margin-top: 40px;
letter-spacing: 2px;
display: inline-block;
text-transform: uppercase;
color: ${themeCfg.primary};
}
.cover-footer {
font-size: 10px;
font-weight: 900;
letter-spacing: 4px;
color: ${themeCfg.primary};
margin-bottom: 0.5in;
}
/* Copyright page styling */
.copyright-page h3 {
font-size: 12px;
font-weight: 800;
margin-bottom: 8px;
text-transform: uppercase;
color: ${themeCfg.primary};
}
.copyright-page p {
font-size: 10px;
line-height: 1.5;
color: #111;
text-align: justify;
margin: 0 0 18px 0;
}
/* TOC table */
.toc-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 30px;
}
.toc-item {
display: flex;
justify-content: space-between;
font-size: 10px;
font-weight: bold;
color: ${themeCfg.primary};
}
.dots {
flex-grow: 1;
text-align: center;
color: ${themeCfg.primary};
overflow: hidden;
white-space: nowrap;
margin: 0 10px;
}
/* Chapter divisions cover */
.chapter-cover {
justify-content: space-between;
padding: 1.2in 0.6in;
text-align: center;
border: 2px solid ${themeCfg.border};
}
.chapter-cover-interior {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 2in;
}
.chapter-number {
font-size: 12px;
font-weight: 900;
letter-spacing: 4px;
color: ${themeCfg.primary};
}
.chapter-cover h2 {
font-family: 'Georgia', serif;
font-size: 22px;
font-weight: 900;
margin-top: 20px;
color: ${themeCfg.primary};
}
/* Chapter text summaries */
.chapter-content {
font-family: 'Georgia', serif;
line-height: 1.7;
}
.chapter-text-interior {
padding: 30px 10px;
text-align: justify;
}
.chapter-text-interior h3 {
font-size: 13px;
font-weight: 900;
border-bottom: 2px solid ${themeCfg.border};
padding-bottom: 6px;
margin-bottom: 18px;
letter-spacing: 1px;
color: ${themeCfg.primary};
}
.chapter-text-interior p {
font-size: 11px;
line-height: 1.7;
color: #000;
margin-bottom: 14px;
}
.chapter-text-interior li {
font-size: 11px;
margin-left: 15px;
list-style-type: none;
margin-bottom: 8px;
color: ${themeCfg.primary};
font-weight: 600;
}
/* High-Contrast Workbook Elements */
h2 {
font-size: 14px;
font-weight: 900;
text-align: center;
margin: 0 0 5px 0;
letter-spacing: 1px;
text-transform: uppercase;
color: ${themeCfg.primary};
}
.divider {
height: 3px;
background: ${themeCfg.gradient};
width: 40px;
margin: 0 auto 10px auto;
}
.box {
border: 2px solid ${themeCfg.border};
border-radius: 6px;
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 12px;
background: ${themeCfg.bgLight};
}
.label {
font-size: 9px;
font-weight: 900;
letter-spacing: 1.5px;
color: ${themeCfg.primary};
border-bottom: 2px solid ${themeCfg.border};
padding-bottom: 4px;
text-transform: uppercase;
}
.line {
border-bottom: 2px dotted ${themeCfg.border};
height: 20px;
width: 100%;
}
.checkbox-row {
display: flex;
align-items: center;
gap: 12px;
}
.checkbox {
width: 16px;
height: 16px;
border: 2.5px solid ${themeCfg.border};
border-radius: 3px;
flex-shrink: 0;
background: #fff;
}
.doodle-placeholder {
border: 2px dashed ${themeCfg.border};
border-radius: 4px;
flex-grow: 1;
min-height: 80px;
background: #fff;
}
.habit-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.circles {
font-size: 15px;
letter-spacing: 4px;
color: ${themeCfg.primary};
}
/* Dot Grid journaling */
.dot-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-gap: 25px;
padding: 30px 40px;
opacity: 0.55;
flex-grow: 1;
}
.dot {
font-size: 26px;
text-align: center;
line-height: 0;
color: ${themeCfg.border};
font-weight: 900;
}
/* Priority Matrix 2x2 grid */
.matrix-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-gap: 15px;
flex-grow: 1;
padding: 10px 0;
}
.matrix-box {
border: 2px solid ${themeCfg.border};
border-radius: 6px;
padding: 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
background: ${themeCfg.bgLight};
}
/* Double Column budget/case tables */
.budget-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.budget-table th {
font-size: 9px;
font-weight: 900;
text-align: left;
padding: 8px;
border-bottom: 3px solid ${themeCfg.border};
text-transform: uppercase;
color: ${themeCfg.primary};
}
.budget-table td {
padding: 12px 8px;
border-bottom: 2px solid ${themeCfg.border};
font-size: 11px;
color: #000;
}
.page-number {
text-align: center;
font-size: 10px;
color: ${themeCfg.primary};
border-top: 2px solid ${themeCfg.border};
padding-top: 6px;
font-weight: 700;
}
</style>
</head>
<body>
${pagesHtml}
<script>
window.onload = function() { window.print(); }
</script>
</body>
</html>
`;
};
const createProductZip = ({
title, subtitle, description, tags, cta, price, platform, productContent, coverBuffer, safeTitle, theme = 'classic'
}) => {
return new Promise((resolve, reject) => {
const zipFilename = `${safeTitle}_${Date.now()}.zip`;
const zipPath = path.join(packagesDir, zipFilename);
const output = fs.createWriteStream(zipPath);
const archive = new ZipArchive({ zlib: { level: 9 } });
output.on('close', () => resolve({ zipFilename, zipPath }));
archive.on('error', reject);
archive.pipe(output);
// 1. Book content as markdown
archive.append(productContent, { name: 'book_content.md' });
// 2. Cover image (if generated)
if (coverBuffer) {
const isSvg = coverBuffer.toString().trim().startsWith('<svg');
const filename = isSvg ? 'cover.svg' : 'cover.jpg';
archive.append(coverBuffer, { name: filename });
}
// 3. Premium 120-Page print-ready HTML Interior
try {
const compiledHtml = compile120PageHtml(title, subtitle, tags, productContent, theme);
archive.append(compiledHtml, { name: 'printable_120_page_interior.html' });
} catch (htmlErr) {
console.error('[ZIP Package] Error appending compiled HTML interior:', htmlErr);
}
// 4. Listing metadata as plain text
const isSvgCover = coverBuffer ? coverBuffer.toString().trim().startsWith('<svg') : false;
const coverFilename = isSvgCover ? 'cover.svg' : 'cover.jpg';
const listing = [
`TITLE: ${title}`,
`SUBTITLE: ${subtitle}`,
`PRICE: $${price}`,
`PLATFORM: ${platform}`,
``,
`DESCRIPTION:`,
description,
``,
`TAGS: ${tags}`,
``,
`CALL TO ACTION: ${cta || ''}`,
``,
`--- READY TO PUBLISH ---`,
`Copy the above fields into your store listing.`,
`Upload ${coverFilename} as your product thumbnail.`,
`Attach or link book_content.md as the downloadable product file.`,
`Alternatively, print or save printable_120_page_interior.html as a stunning PDF layout.`,
].join('\n');
archive.append(listing, { name: 'listing.txt' });
archive.finalize();
});
};
// ----------------------------------------------------
// AI AGENT AUTOPILOT ENGINE β€” DAILY PACKAGE GENERATOR
// ----------------------------------------------------
let autopilotInterval = null;
let autopilotThoughts = ['[SYSTEM] πŸ” Daily Package Generator loaded. Runs on schedule to create ready-to-publish digital products.'];
const autopilotProducts = [
{ niche: 'Python for Engineering Students: 50 Practical Programs with Explanations', platform: 'gumroad', type: 'coding_tutorial' },
{ niche: '90-Day Indian Weight Loss Plan with Budget-Friendly Recipes', platform: 'etsy', type: 'planner' },
{ niche: 'AWS Certified Solutions Architect Exam Study Guide & Practice Notes', platform: 'kdp', type: 'study_guide' },
{ niche: 'ADHD Daily Study Planner & Focus Tracker Journal', platform: 'etsy', type: 'planner' },
{ niche: 'ChatGPT Prompt Engineering Masterclass: 100+ Copy-Paste Prompts', platform: 'gumroad', type: 'study_guide' },
{ niche: 'Resume & Interview Prep Guide: The Tech Industry Playbook', platform: 'payhip', type: 'study_guide' },
{ niche: 'React & Next.js Complete Project Tutorial for Computer Science Students', platform: 'gumroad', type: 'coding_tutorial' },
{ niche: 'Large Print Cognitive Activity Book & Mind Exercises for Seniors', platform: 'kdp', type: 'puzzle_book' },
{ niche: 'Wedding Planning Ultimate Budget & Checklist Bundle', platform: 'etsy', type: 'planner' }
];
const runAutopilotSequence = async () => {
const timestamp = new Date().toLocaleTimeString();
const selected = autopilotProducts[Math.floor(Math.random() * autopilotProducts.length)];
const selectedNiche = selected.niche;
const selectedPlatform = selected.platform;
const strategy = PLATFORM_STRATEGIES[selectedPlatform] || PLATFORM_STRATEGIES.gumroad;
console.log(`πŸ€– Daily Generator: Creating package for '${selectedNiche}'...`);
autopilotThoughts.unshift(`[${timestamp}] ${strategy.emoji} Daily Generator β†’ Writing "${selectedNiche}"...`);
if (autopilotThoughts.length > 40) autopilotThoughts.pop();
if (!process.env.GROQ_API_KEY) {
autopilotThoughts.unshift(`[${timestamp}] ❌ Aborted: GROQ_API_KEY missing.`);
return;
}
try {
const prompt = `Create a complete, humanized digital product for: "${selectedNiche}" (targeting ${strategy.name} buyers).
CRITICAL ORIGINALITY & COPYRIGHT DIRECTIVES:
- Content must be 100% original, creative, and written from scratch. Do NOT copy, paraphrase, or borrow from any copyrighted books, publications, or trademarked products.
- Focus on high-value, highly practical content (e.g., comprehensive study guides, structured planners, templates, step-by-step how-to guides, and engaging hobby/educational materials) rather than low-quality generic text.
- If this guide contains technical, factual, or educational information, ensure all facts are reviewed, verified, and include realistic, helpful examples and citations where appropriate.
- COMPLIANCE & DISCLOSURE: Disclose AI assistance transparently as required by platform policies (e.g. KDP/Etsy). Add a small, elegant "Research & Editorial Note: Designed with the assistance of advanced generative tools and verified by human subject matter experts for accuracy" to the product introduction.
Your response must be a single, valid JSON object matching this schema EXACTLY:
{
"title": "A viral, specific title optimized for self-publishing (e.g. '90-Day Indian Weight Loss Plan' instead of generic 'Weight Loss'). Max 80 characters.",
"subtitle": "A benefit-driven subtitle with an emotional hook. Max 150 characters.",
"description": "A 150-word high-converting product description with emotional hooks and bullet benefits.",
"tags": "An array of exactly 5 comma-separated SEO tags for ${strategy.name}.",
"cta": "One powerful call-to-action sentence (max 20 words).",
"thumbnailPrompt": "A detailed Stable Diffusion prompt for a stunning professional product cover image. Include style, colors, composition. Keep under 80 words.",
"productContent": "The actual complete high-quality digital book. Use this EXACT structure:
- Detailed Table of Contents (listing all 5 chapters)
- Chapter 1 - Introduction (Comprehensive introductory text in easy-to-read English, short paragraphs, real-world examples, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 2 - Basics (Solid explanation of core concepts, foundational steps, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 3 - Advanced Techniques (High-level professional strategies, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 4 - Case Studies (Actionable case studies, templates, or checklists, ending with a 'Key Points' summary with checkmarks 'βœ“')
- Chapter 5 - Conclusion (Final takeaways, future roadmaps, ending with a 'Key Points' summary with checkmarks 'βœ“')"
}
Ensure the "productContent" is extremely comprehensive, highly valuable, and direct, avoiding generic AI repetitions or empty introductory filler phrases. Let it be a complete, ready-to-sell book of at least 500 words.`;
const data = await groqJsonChat(prompt, 4000);
const parsedTitle = data.title || selectedNiche;
const parsedSubtitle = data.subtitle || '';
const parsedDescription = data.description || '';
const parsedTags = Array.isArray(data.tags) ? data.tags.join(', ') : (data.tags || '');
const parsedCta = data.cta || strategy.cta;
const thumbnailPrompt = data.thumbnailPrompt || '';
let productContent = data.productContent || 'Content generation failed.';
// STEP 1.5: Humanizer Pass to make it 100% human-like
if (productContent && productContent !== 'Content generation failed.') {
autopilotThoughts.unshift(`[${timestamp}] ✍️ Humanizer Pass β†’ Refining book to 100% human-like tone...`);
try {
productContent = await humanizeBookContent(productContent);
autopilotThoughts.unshift(`[${timestamp}] ✨ Humanizer Pass complete!`);
} catch (hErr) {
console.error('Autopilot Humanizer Pass failed:', hErr);
autopilotThoughts.unshift(`[${timestamp}] ⚠️ Humanizer Pass skipped: ${hErr.message}`);
}
}
const recommendedPrice = parseFloat((Math.random() * (strategy.priceMax - strategy.priceMin) + strategy.priceMin).toFixed(2));
const safeTitle = parsedTitle.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase().slice(0, 40);
autopilotThoughts.unshift(`[${timestamp}] βœ… Book written! ${productContent.split(' ').length} words β€” "${parsedTitle.slice(0, 35)}..."`);
// STEP 2: Generate cover image via Resilient HF Model Chain
let coverBuffer = null;
let coverUrl = '';
if (process.env.HF_API_KEY && thumbnailPrompt) {
autopilotThoughts.unshift(`[${timestamp}] 🎨 HF β†’ Initiating resilient cover art generation...`);
const models = [
'black-forest-labs/FLUX.1-schnell',
'stabilityai/stable-diffusion-xl-base-1.0',
'stabilityai/stable-diffusion-3.5-medium'
];
let responseBlob = null;
for (const model of models) {
try {
console.log(`[HF Autopilot] Attempting cover generation with model: ${model}...`);
responseBlob = await hf.textToImage({
model: model,
inputs: thumbnailPrompt,
parameters: model.includes('flux') ? {} : {
negative_prompt: 'blurry, low quality, distorted, text, watermark'
}
});
console.log(`[HF Autopilot] Successfully generated cover using model: ${model}`);
break;
} catch (err) {
console.warn(`[HF Autopilot] Model ${model} failed: ${err.message}`);
}
}
if (responseBlob) {
try {
const arrayBuffer = await responseBlob.arrayBuffer();
coverBuffer = Buffer.from(arrayBuffer);
const coverFilename = `${safeTitle}_${Date.now()}.jpg`;
const coverPath = path.join(coversDir, coverFilename);
fs.writeFileSync(coverPath, coverBuffer);
const spaceUrl = process.env.SPACE_ID ? `https://${process.env.SPACE_ID.replace('/', '-')}.hf.space` : 'http://localhost:5000';
coverUrl = `${spaceUrl}/static/covers/${coverFilename}`;
autopilotThoughts.unshift(`[${timestamp}] πŸ–ΌοΈ Cover generated successfully!`);
} catch (bufErr) {
console.error('[HF Autopilot] Error processing response buffer:', bufErr);
}
} else {
// Fallback Vector SVG Cover
console.log(`[HF Autopilot] All models failed. Generating premium vector SVG fallback cover...`);
autopilotThoughts.unshift(`[${timestamp}] ⚠️ HF models rate-limited. Generating premium vector SVG cover...`);
const svgContent = `
<svg width="600" height="900" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8b5cf6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="600" height="900" fill="url(#grad)" />
<rect x="30" y="30" width="540" height="840" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="4" rx="10" />
<!-- Brand Header -->
<rect x="250" y="80" width="100" height="24" fill="rgba(255,255,255,0.1)" rx="12" />
<text x="300" y="96" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="10" font-weight="bold" fill="rgba(255,255,255,0.8)" text-anchor="middle" letter-spacing="2">BOOKFORGE</text>
<!-- Dynamic Title -->
<foreignObject x="60" y="240" width="480" height="350">
<div xmlns="http://www.w3.org/1999/xhtml" style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:42px; font-weight:800; color:#ffffff; text-align:center; line-height:1.2; text-shadow:0 4px 12px rgba(0,0,0,0.3);">
${(parsedTitle || 'BOOKFORGE DIGITAL').toUpperCase()}
</div>
</foreignObject>
<!-- Footer Sub-branding -->
<line x1="200" y1="650" x2="400" y2="650" stroke="rgba(255,255,255,0.3)" stroke-width="2" />
<text x="300" y="690" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle" letter-spacing="4">PREMIUM EDITION</text>
<text x="300" y="720" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="11" fill="rgba(255,255,255,0.6)" text-anchor="middle">COMPLETE STUDY GUIDE &amp; PLANNER</text>
</svg>
`;
coverBuffer = Buffer.from(svgContent);
const coverFilename = `${safeTitle}_${Date.now()}.svg`;
const coverPath = path.join(coversDir, coverFilename);
fs.writeFileSync(coverPath, coverBuffer);
const spaceUrl = process.env.SPACE_ID ? `https://${process.env.SPACE_ID.replace('/', '-')}.hf.space` : 'http://localhost:5000';
coverUrl = `${spaceUrl}/static/covers/${coverFilename}`;
autopilotThoughts.unshift(`[${timestamp}] πŸ–ΌοΈ Premium vector SVG cover created!`);
}
}
// STEP 3: Create ZIP package
autopilotThoughts.unshift(`[${timestamp}] πŸ“¦ Packaging into ZIP (book + cover + listing)...`);
const { zipFilename } = await createProductZip({
title: parsedTitle,
subtitle: parsedSubtitle,
description: parsedDescription,
tags: parsedTags,
cta: parsedCta,
price: recommendedPrice,
platform: selectedPlatform,
productContent,
coverBuffer,
safeTitle
});
const spaceUrl = process.env.SPACE_ID ? `https://${process.env.SPACE_ID.replace('/', '-')}.hf.space` : 'http://localhost:5000';
const packageUrl = `${spaceUrl}/static/packages/${zipFilename}`;
autopilotThoughts.unshift(`[${timestamp}] πŸ“¦ ZIP ready! πŸ”— ${packageUrl}`);
// STEP 4: Save to catalog
const catalogEntry = {
title: parsedTitle,
subtitle: parsedSubtitle,
type: selected.type,
price: recommendedPrice,
platform: selectedPlatform,
tags: parsedTags,
sales_copy: parsedDescription,
thumbnail_url: coverUrl,
status: 'ready_to_publish',
published_url: packageUrl // stores the ZIP download URL
};
if (isSupabaseConnected) {
try {
const { error } = await supabase.from('catalog').insert([catalogEntry]);
if (error) throw error;
autopilotThoughts.unshift(`[${timestamp}] βœ… Saved to catalog! Download ZIP to publish manually.`);
} catch (err) {
const catalog = readDb();
catalog.unshift({ id: Date.now().toString(), ...catalogEntry, date: new Date().toLocaleDateString() });
writeDb(catalog);
autopilotThoughts.unshift(`[${timestamp}] βœ… Saved to local catalog.`);
}
} else {
const catalog = readDb();
catalog.unshift({ id: Date.now().toString(), ...catalogEntry, date: new Date().toLocaleDateString() });
writeDb(catalog);
autopilotThoughts.unshift(`[${timestamp}] βœ… Saved to local catalog.`);
}
} catch (err) {
console.error('Daily Generator Error:', err);
autopilotThoughts.unshift(`[${timestamp}] ❌ Error: ${err.message}`);
}
};
app.get('/api/autopilot/status', (req, res) => {
res.json({
active: !!autopilotInterval,
thoughts: autopilotThoughts
});
});
app.post('/api/autopilot/toggle', (req, res) => {
const { active, intervalMs = 86400000 } = req.body; // Default 24 hours
if (active) {
if (autopilotInterval) clearInterval(autopilotInterval);
runAutopilotSequence(); // Run immediately on activation
autopilotInterval = setInterval(runAutopilotSequence, intervalMs);
autopilotThoughts.unshift(`[${new Date().toLocaleTimeString()}] πŸš€ Daily Generator ACTIVATED. Running every ${Math.round(intervalMs / 3600000)}h. Generating first package now...`);
} else {
if (autopilotInterval) { clearInterval(autopilotInterval); autopilotInterval = null; }
autopilotThoughts.unshift(`[${new Date().toLocaleTimeString()}] πŸ›‘ Daily Generator STOPPED.`);
}
res.json({ active: !!autopilotInterval });
});
// ----------------------------------------------------
// GUMROAD PUBLISHING AGENT ENGINE
// ----------------------------------------------------
const publishToGumroad = async ({ title, description, price, productUrl, coverUrl, token }) => {
if (!token) throw new Error('Gumroad Access Token is required');
console.log(`[GUMROAD] Creating product "${title}"...`);
// 1. Create redirect product
const createRes = await fetch('https://api.gumroad.com/v2/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
access_token: token,
name: title,
price: Math.round(price * 100), // in cents
description: description,
url: productUrl, // redirect URL
redirect_to_external_url: true // Tells Gumroad to redirect buyers to the URL after purchase
})
});
const createData = await createRes.json();
if (!createRes.ok || !createData.success) {
throw new Error((createData && createData.message) || 'Failed to create product on Gumroad');
}
const gumroadProduct = createData.product;
const productId = gumroadProduct.id;
console.log(`[GUMROAD] Product created. ID: ${productId}`);
// 2. Attach Cover Image (if coverUrl is provided)
if (coverUrl) {
console.log(`[GUMROAD] Attaching cover image from: ${coverUrl}...`);
const coverRes = await fetch(`https://api.gumroad.com/v2/products/${productId}/covers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
access_token: token,
url: coverUrl
})
});
const coverData = await coverRes.json();
if (!coverRes.ok || !coverData.success) {
console.warn(`[GUMROAD] Warning: Failed to attach cover image: ${coverData ? coverData.message : 'Unknown'}`);
} else {
console.log(`[GUMROAD] Cover image attached successfully.`);
}
}
// 3. Publish/Enable the product
console.log(`[GUMROAD] Enabling/publishing product...`);
const enableRes = await fetch(`https://api.gumroad.com/v2/products/${productId}/enable`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
access_token: token
})
});
const enableData = await enableRes.json();
if (!enableRes.ok || !enableData.success) {
throw new Error((enableData && enableData.message) || 'Failed to enable product on Gumroad');
}
console.log(`[GUMROAD] Product published! Short URL: ${gumroadProduct.short_url}`);
return {
productId,
publishedUrl: gumroadProduct.short_url
};
};
app.post('/api/publish/gumroad', async (req, res) => {
const { id, title, description, price, productUrl, coverUrl, token } = req.body;
if (!token) return res.status(400).json({ error: 'Gumroad Access Token is required' });
try {
const result = await publishToGumroad({
title,
description,
price,
productUrl,
coverUrl,
token
});
// Update database status if ID exists
if (id) {
if (isSupabaseConnected) {
await supabase
.from('catalog')
.update({
status: 'published',
published_url: result.publishedUrl,
thumbnail_url: coverUrl || ''
})
.eq('id', id);
} else {
const catalog = readDb();
const item = catalog.find(x => x.id === id);
if (item) {
item.status = 'published';
item.published_url = result.publishedUrl;
item.thumbnail_url = coverUrl || '';
writeDb(catalog);
}
}
}
res.json({ success: true, publishedUrl: result.publishedUrl });
} catch (err) {
console.error('Gumroad Publishing Error:', err);
res.status(500).json({ error: err.message });
}
});
// Manual on-demand package generator (called from Marketplace frontend)
app.post('/api/generate-package', async (req, res) => {
const { title, subtitle, description, tags, cta, price, platform, productContent, thumbnailPrompt, theme } = req.body;
if (!title || !productContent) return res.status(400).json({ error: 'title and productContent required' });
try {
const safeTitle = title.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase().slice(0, 40);
let coverBuffer = null;
// Generate cover if prompt provided and HF key exists
if (thumbnailPrompt && process.env.HF_API_KEY) {
const models = [
'black-forest-labs/FLUX.1-schnell',
'stabilityai/stable-diffusion-xl-base-1.0',
'stabilityai/stable-diffusion-3.5-medium'
];
let responseBlob = null;
for (const model of models) {
try {
console.log(`[HF Package] Attempting cover generation with model: ${model}...`);
responseBlob = await hf.textToImage({
model: model,
inputs: thumbnailPrompt,
parameters: model.includes('flux') ? {} : {
negative_prompt: 'blurry, low quality, distorted, text, watermark'
}
});
console.log(`[HF Package] Successfully generated cover using model: ${model}`);
break;
} catch (err) {
console.warn(`[HF Package] Model ${model} failed: ${err.message}`);
}
}
if (responseBlob) {
try {
coverBuffer = Buffer.from(await responseBlob.arrayBuffer());
const coverFilename = `${safeTitle}_${Date.now()}.jpg`;
fs.writeFileSync(path.join(coversDir, coverFilename), coverBuffer);
} catch (bufErr) {
console.error('[HF Package] Error processing response buffer:', bufErr);
}
} else {
// Fallback Vector SVG Cover
console.log(`[HF Package] All models failed. Generating premium vector SVG fallback cover...`);
const svgContent = `
<svg width="600" height="900" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8b5cf6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#d97706;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="600" height="900" fill="url(#grad)" />
<rect x="30" y="30" width="540" height="840" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="4" rx="10" />
<!-- Brand Header -->
<rect x="250" y="80" width="100" height="24" fill="rgba(255,255,255,0.1)" rx="12" />
<text x="300" y="96" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="10" font-weight="bold" fill="rgba(255,255,255,0.8)" text-anchor="middle" letter-spacing="2">BOOKFORGE</text>
<!-- Dynamic Title -->
<foreignObject x="60" y="240" width="480" height="350">
<div xmlns="http://www.w3.org/1999/xhtml" style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:42px; font-weight:800; color:#ffffff; text-align:center; line-height:1.2; text-shadow:0 4px 12px rgba(0,0,0,0.3);">
${(title || 'BOOKFORGE DIGITAL').toUpperCase()}
</div>
</foreignObject>
<!-- Footer Sub-branding -->
<line x1="200" y1="650" x2="400" y2="650" stroke="rgba(255,255,255,0.3)" stroke-width="2" />
<text x="300" y="690" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle" letter-spacing="4">PREMIUM EDITION</text>
<text x="300" y="720" font-family="'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="11" fill="rgba(255,255,255,0.6)" text-anchor="middle">COMPLETE STUDY GUIDE &amp; PLANNER</text>
</svg>
`;
coverBuffer = Buffer.from(svgContent);
const coverFilename = `${safeTitle}_${Date.now()}.svg`;
fs.writeFileSync(path.join(coversDir, coverFilename), coverBuffer);
}
}
const { zipFilename } = await createProductZip({
title, subtitle, description, tags, cta, price, platform, productContent, coverBuffer, safeTitle, theme
});
const baseUrl = getBaseUrl(req);
const packageUrl = `${baseUrl}/static/packages/${zipFilename}`;
res.json({ success: true, packageUrl, zipFilename });
} catch (err) {
console.error('Package generation error:', err);
res.status(500).json({ error: err.message });
}
});
// Start Server
app.listen(PORT, () => {
console.log(`πŸš€ KDP AI Factory Backend running at http://localhost:${PORT}`);
console.log(`πŸ€– Groq Llama 3.3 70B: ${process.env.GROQ_API_KEY ? 'CONFIGURED βœ…' : 'NOT CONFIGURED ❌ (Add GROQ_API_KEY to .env)'}`);
console.log(`🎨 HuggingFace SDXL: ${process.env.HF_API_KEY ? 'CONFIGURED βœ…' : 'NOT CONFIGURED ❌ (Add HF_API_KEY to .env)'}`);
});