// BlogWizard Pro - Main JavaScript class BlogGenerator { constructor() { this.currentBlog = ''; this.currentStep = 1; this.totalSteps = 4; this.viewMode = 'editor'; // 'editor' or 'preview' this.apiKeys = {}; this.outlineStructure = []; this.initializeEventListeners(); this.loadSettings(); this.initDarkMode(); this.initMultiStepForm(); this.loadApiKeys(); } initializeEventListeners() { // Form submission document.getElementById('blogForm').addEventListener('submit', (e) => { e.preventDefault(); this.generateBlog(); }); // Blog output changes document.getElementById('blogOutput').addEventListener('input', (e) => { this.updateWordCount(); this.currentBlog = e.target.value; this.updatePreview(); }); // Model change document.getElementById('model').addEventListener('change', (e) => { localStorage.setItem('selectedModel', e.target.value); }); } initMultiStepForm() { this.updateStepDisplay(); } initDarkMode() { const html = document.documentElement; // Check for saved preference const darkMode = localStorage.getItem('darkMode') === 'true'; if (darkMode) { html.classList.add('dark'); } } loadSettings() { const savedModel = localStorage.getItem('selectedModel'); const savedProvider = localStorage.getItem('aiProvider'); if (savedModel) { document.getElementById('model').value = savedModel; } // Update placeholder based on selected model this.updateApiKeyPlaceholder(document.getElementById('model').value); // Add model change listener to update placeholder document.getElementById('model').addEventListener('change', (e) => { this.updateApiKeyPlaceholder(e.target.value); this.autoSwitchApiKey(e.target.value); }); } loadApiKeys() { const savedKeys = localStorage.getItem('apiKeys'); if (savedKeys) { this.apiKeys = JSON.parse(savedKeys); this.displaySavedKeys(); } } displaySavedKeys() { const savedKeysDiv = document.getElementById('savedKeys'); if (Object.keys(this.apiKeys).length === 0) { savedKeysDiv.innerHTML = '

No saved API keys yet

'; return; } savedKeysDiv.innerHTML = Object.entries(this.apiKeys).map(([model, key]) => `
${model.replace('-', ' ').toUpperCase()}: ${key.substring(0, 10)}...
`).join(''); } saveApiKeyModel(model, key) { this.apiKeys[model] = key; localStorage.setItem('apiKeys', JSON.stringify(this.apiKeys)); this.displaySavedKeys(); this.showToast(`API key saved for ${model}`, 'success'); } autoSwitchApiKey(model) { if (this.apiKeys[model]) { document.getElementById('apiKey').value = this.apiKeys[model]; this.validateApiKey(this.apiKeys[model]); } } updateStepDisplay() { // Hide all steps for (let i = 1; i <= this.totalSteps; i++) { document.getElementById(`step${i}`).classList.add('hidden'); document.getElementById(`step${i}Indicator`).classList.remove('active'); } // Show current step document.getElementById(`step${this.currentStep}`).classList.remove('hidden'); document.getElementById(`step${this.currentStep}Indicator`).classList.add('active'); // Update navigation buttons const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); const generateBtn = document.getElementById('generateBtn'); if (this.currentStep === 1) { prevBtn.classList.add('hidden'); nextBtn.classList.remove('hidden'); generateBtn.classList.add('hidden'); } else if (this.currentStep === this.totalSteps) { prevBtn.classList.remove('hidden'); nextBtn.classList.add('hidden'); generateBtn.classList.remove('hidden'); this.updateSummary(); } else { prevBtn.classList.remove('hidden'); nextBtn.classList.remove('hidden'); generateBtn.classList.add('hidden'); } } updateSummary() { const topic = document.getElementById('topic').value; const keywords = document.getElementById('keywords').value; const tone = document.getElementById('tone').value; const length = document.getElementById('length').value; const language = document.getElementById('language').value; const model = document.getElementById('model').value; const summary = document.getElementById('summary'); summary.innerHTML = `
Topic: ${topic || 'Not specified'}
Keywords: ${keywords || 'None'}
Tone: ${tone.charAt(0).toUpperCase() + tone.slice(1)}
Length: ${length.charAt(0).toUpperCase() + length.slice(1)}
Language: ${this.getLanguageName(language)}
Model: ${model.replace('-', ' ').toUpperCase()}
`; } getLanguageName(code) { const languages = { 'en': 'English', 'es': 'Spanish', 'fr': 'French', 'de': 'German', 'zh': 'Chinese' }; return languages[code] || code; } updateApiKeyPlaceholder(model) { const apiKeyInput = document.getElementById('apiKey'); if (model.startsWith('gemini')) { apiKeyInput.placeholder = 'Enter Gemini API key...'; } else if (model.startsWith('claude')) { apiKeyInput.placeholder = 'sk-ant-...'; } else { apiKeyInput.placeholder = 'sk-...'; } } async generateBlog() { const topic = document.getElementById('topic').value; const keywords = document.getElementById('keywords').value; const tone = document.getElementById('tone').value; const length = document.getElementById('length').value; const language = document.getElementById('language').value; const apiKey = document.getElementById('apiKey').value; const model = document.getElementById('model').value; if (!apiKey) { this.showToast('Please enter your API key in settings', 'error'); return; } // Show loading state const outputSection = document.getElementById('outputSection'); const loadingSpinner = document.getElementById('loadingSpinner'); const blogOutput = document.getElementById('blogOutput'); outputSection.classList.remove('hidden'); outputSection.classList.add('slide-in'); loadingSpinner.classList.remove('hidden'); blogOutput.value = ''; // Create prompt const prompt = this.createPrompt(topic, keywords, tone, length, language); try { // Simulate API call (replace with actual API call) const generatedContent = await this.callAI(prompt, apiKey, model); setTimeout(() => { loadingSpinner.classList.add('hidden'); blogOutput.value = generatedContent; this.currentBlog = generatedContent; this.updateWordCount(); this.updateTimestamp(); this.showToast('Blog generated successfully!', 'success'); }, 2000); } catch (error) { loadingSpinner.classList.add('hidden'); this.showToast('Error generating blog: ' + error.message, 'error'); } } createPrompt(topic, keywords, tone, length, language) { const wordCounts = { short: '300-500', medium: '500-800', long: '800-1200' }; return `Write a blog post about "${topic}". Keywords to include: ${keywords}. Tone: ${tone}. Length: ${wordCounts[length]} words. Language: ${language}. Make it engaging and well-structured with an introduction, body paragraphs, and conclusion. Include a catchy title.`; } async callAI(prompt, apiKey, model) { // Check if it's a Gemini model if (model.startsWith('gemini')) { return await this.callGemini(prompt, apiKey, model); } else if (model.startsWith('gpt')) { return await this.callOpenAI(prompt, apiKey, model); } else if (model.startsWith('claude')) { return await this.callClaude(prompt, apiKey, model); } // Fallback to Gemini if model not recognized return await this.callGemini(prompt, apiKey, 'gemini-pro'); } async callOpenAI(prompt, apiKey, model) { try { const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: model, messages: [{ role: 'user', content: prompt }], max_tokens: 2000, temperature: 0.7 }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`); } const data = await response.json(); return data.choices[0].message.content; } catch (error) { if (error.message.includes('API key')) { throw new Error('Invalid OpenAI API key. Please check your API key and try again.'); } else if (error.message.includes('quota')) { throw new Error('OpenAI API quota exceeded. Please check your usage and billing.'); } else { throw new Error(`OpenAI API error: ${error.message}`); } } } async callClaude(prompt, apiKey, model) { try { const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, body: JSON.stringify({ model: model, max_tokens: 2000, messages: [{ role: 'user', content: prompt }] }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`); } const data = await response.json(); return data.content[0].text; } catch (error) { if (error.message.includes('API key')) { throw new Error('Invalid Claude API key. Please check your API key and try again.'); } else if (error.message.includes('quota')) { throw new Error('Claude API quota exceeded. Please check your usage and billing.'); } else { throw new Error(`Claude API error: ${error.message}`); } } } async callGemini(prompt, apiKey, model) { try { const response = await fetch(`https://generativelanguage.googleapis.com/v1/models/${model}:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`); } const data = await response.json(); return data.candidates[0].content.parts[0].text; } catch (error) { if (error.message.includes('API key')) { throw new Error('Invalid Gemini API key. Please check your API key and try again.'); } else if (error.message.includes('quota')) { throw new Error('Gemini API quota exceeded. Please check your usage and billing.'); } else { throw new Error(`Gemini API error: ${error.message}`); } } } updateWordCount() { const blogOutput = document.getElementById('blogOutput'); const text = blogOutput.value; const wordCount = text.trim() ? text.trim().split(/\s+/).length : 0; const readingTime = Math.max(1, Math.ceil(wordCount / 200)); // Average reading speed: 200 words/min document.getElementById('wordCount').textContent = `${wordCount} words`; document.getElementById('readingTime').textContent = `${readingTime} min read`; // Calculate readability score (simplified Flesch Reading Ease) if (text) { const sentences = text.split(/[.!?]+/).length; const avgWordsPerSentence = wordCount / sentences; const avgSyllables = this.countSyllables(text) / wordCount; const score = 206.835 - (1.015 * avgWordsPerSentence) - (84.6 * avgSyllables); let readability = Math.round(score); if (readability > 100) readability = 100; if (readability < 0) readability = 0; let level = 'Very Difficult'; if (readability >= 90) level = 'Very Easy'; else if (readability >= 80) level = 'Easy'; else if (readability >= 70) level = 'Fairly Easy'; else if (readability >= 60) level = 'Standard'; else if (readability >= 50) level = 'Fairly Difficult'; else if (readability >= 30) level = 'Difficult'; document.getElementById('readabilityScore').textContent = `Score: ${readability} (${level})`; } else { document.getElementById('readabilityScore').textContent = 'Score: --'; } } countSyllables(text) { const words = text.toLowerCase().split(/\s+/); let syllableCount = 0; words.forEach(word => { word = word.replace(/[^a-z]/g, ''); if (word.length === 0) return; let count = 0; let prevCharWasVowel = false; const vowels = 'aeiouy'; for (let i = 0; i < word.length; i++) { const isVowel = vowels.includes(word[i]); if (isVowel && !prevCharWasVowel) { count++; } prevCharWasVowel = isVowel; } if (word.endsWith('e')) count--; if (count === 0) count = 1; syllableCount += count; }); return syllableCount; } updateTimestamp() { const now = new Date(); const timestamp = now.toLocaleString(); document.getElementById('timestamp').textContent = `Generated on ${timestamp}`; } regenerateBlog() { this.generateBlog(); } copyBlog() { const blogOutput = document.getElementById('blogOutput'); blogOutput.select(); document.execCommand('copy'); this.showToast('Blog copied to clipboard!', 'success'); } downloadBlog(format) { const blogOutput = document.getElementById('blogOutput'); const topic = document.getElementById('topic').value || 'blog'; const timestamp = new Date().toISOString().slice(0, 10); const filename = `${topic}-${timestamp}.${format}`; let content = blogOutput.value; if (format === 'md' && !content.startsWith('#')) { // Add markdown title if not present content = `# ${topic}\n\n${content}`; } const blob = new Blob([content], { type: format === 'md' ? 'text/markdown' : 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); this.showToast(`Blog downloaded as ${format.toUpperCase()}!`, 'success'); } formatText(formatType) { const textarea = document.getElementById('blogOutput'); const start = textarea.selectionStart; const end = textarea.selectionEnd; const selectedText = textarea.value.substring(start, end); let formattedText = ''; switch(formatType) { case 'bold': formattedText = `**${selectedText}**`; break; case 'italic': formattedText = `*${selectedText}*`; break; case 'heading': formattedText = selectedText ? `## ${selectedText}` : '## '; break; case 'list': formattedText = selectedText ? `- ${selectedText}` : '- '; break; case 'link': const url = prompt('Enter URL:', 'https://'); formattedText = url ? `[${selectedText || 'link text'}](${url})` : selectedText; break; case 'quote': formattedText = selectedText ? `> ${selectedText}` : '> '; break; case 'code': formattedText = selectedText ? `\`${selectedText}\`` : '``'; break; default: formattedText = selectedText; } textarea.value = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end); textarea.selectionStart = textarea.selectionEnd = start + formattedText.length; textarea.focus(); this.currentBlog = textarea.value; this.updateWordCount(); this.updatePreview(); } toggleViewMode() { const editorView = document.getElementById('editorView'); const previewView = document.getElementById('previewView'); const viewModeText = document.getElementById('viewModeText'); const editorToolbar = document.getElementById('editorToolbar'); if (this.viewMode === 'editor') { this.viewMode = 'preview'; editorView.classList.add('hidden'); previewView.classList.remove('hidden'); editorToolbar.classList.add('hidden'); viewModeText.textContent = 'Edit'; this.updatePreview(); } else { this.viewMode = 'editor'; editorView.classList.remove('hidden'); previewView.classList.add('hidden'); editorToolbar.classList.remove('hidden'); viewModeText.textContent = 'Preview'; } } updatePreview() { const content = document.getElementById('blogOutput').value; const preview = document.getElementById('blogPreview'); // Simple markdown to HTML conversion let html = content .replace(/^## (.+)$/gm, '

$1

') .replace(/^# (.+)$/gm, '

$1

') .replace(/^\* (.+)$/gm, '
  • $1
  • ') .replace(/^- (.+)$/gm, '
  • $1
  • ') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/`(.+?)`/g, '$1') .replace(/^> (.+)$/gm, '
    $1
    ') .replace(/\[(.+?)\]\((.+?)\)/g, '$1') .replace(/\n\n/g, '

    ') .replace(/\n/g, '
    '); // Wrap in paragraphs if no HTML tags exist if (!html.includes('<')) { html = `

    ${html}

    `; } preview.innerHTML = html; } showTooltip(field) { const tooltips = { 'topic': 'Enter the main topic or title for your blog post. Be specific for better results.', 'keywords': 'Add relevant keywords to include in your blog. Separate them with commas.', 'tone': 'Choose the writing style: Professional for formal content, Casual for friendly tone, Humorous for entertaining content, Inspirational for motivation, or Technical for detailed explanations.', 'length': 'Select the approximate word count: Short (300-500 words), Medium (500-800 words), or Long (800-1200 words).', 'language': 'Choose the language for your blog post.', 'model': 'Select the AI model to generate your content. Each has unique strengths and capabilities.', 'apiKey': 'Enter your API key for the selected AI model. Your key is stored locally and never shared.' }; const tooltip = document.getElementById('tooltip'); const tooltipContent = document.getElementById('tooltipContent'); const button = event.target.closest('button'); const rect = button.getBoundingClientRect(); tooltipContent.textContent = tooltips[field]; tooltip.style.top = `${rect.bottom + 10}px`; tooltip.style.left = `${rect.left + rect.width / 2}px`; tooltip.style.transform = 'translateX(-50%)'; tooltip.classList.remove('hidden'); // Hide tooltip when clicking outside setTimeout(() => { document.addEventListener('click', function hideTooltip(e) { if (!tooltip.contains(e.target) && !button.contains(e.target)) { tooltip.classList.add('hidden'); document.removeEventListener('click', hideTooltip); } }); }, 100); } validateCurrentStep() { switch(this.currentStep) { case 1: const topic = document.getElementById('topic').value.trim(); if (!topic) { this.showToast('Please enter a blog topic', 'error'); return false; } break; case 3: const apiKey = document.getElementById('apiKey').value.trim(); if (!apiKey) { this.showToast('Please enter your API key', 'error'); return false; } break; } return true; } showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = 'toast'; toast.innerHTML = `
    ${message}
    `; document.body.appendChild(toast); feather.replace(); setTimeout(() => toast.classList.add('show'), 100); setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 3000); } async suggestStructure() { const topic = document.getElementById('topic').value; const keywords = document.getElementById('keywords').value; const length = document.getElementById('length').value; if (!topic) { this.showToast('Please enter a topic first', 'error'); return; } const sections = { short: ['Introduction', 'Main Point', 'Conclusion'], medium: ['Introduction', 'Point 1', 'Point 2', 'Point 3', 'Conclusion'], long: ['Introduction', 'Background', 'Key Concepts', 'Detailed Analysis', 'Case Studies', 'Best Practices', 'Future Outlook', 'Conclusion'] }; this.outlineStructure = sections[length] || sections.medium; const outlineContent = document.getElementById('outlineContent'); outlineContent.innerHTML = this.outlineStructure.map((section, index) => `
    ${index + 1}.
    `).join(''); outlineContent.classList.remove('hidden'); feather.replace(); this.showToast('Structure suggested! You can edit sections above.', 'success'); } updateOutlineSection(index, value) { this.outlineStructure[index] = value; } removeOutlineSection(index) { this.outlineStructure.splice(index, 1); this.suggestStructure(); // Refresh the outline } } // Initialize the app let blogGenerator; document.addEventListener('DOMContentLoaded', () => { blogGenerator = new BlogGenerator(); }); // Global functions for onclick handlers function saveApiKey() { const apiKey = document.getElementById('apiKey').value; const model = document.getElementById('model').value; if (apiKey && model) { blogGenerator.saveApiKeyModel(model, apiKey); } else { blogGenerator.showToast('Please enter an API key', 'error'); } } function useSavedKey(model) { document.getElementById('model').value = model; document.getElementById('apiKey').value = blogGenerator.apiKeys[model]; blogGenerator.validateApiKey(blogGenerator.apiKeys[model]); blogGenerator.showToast('API key loaded', 'success'); } function validateApiKey(key) { const statusSpan = document.getElementById('keyStatus'); if (!key) { statusSpan.innerHTML = ''; return; } let isValid = false; let provider = ''; if (key.startsWith('sk-ant-')) { isValid = key.length > 20; provider = 'Claude'; } else if (key.startsWith('sk-')) { isValid = key.length > 20; provider = 'OpenAI'; } else if (key.length > 20) { isValid = true; provider = 'Gemini'; } if (isValid) { statusSpan.innerHTML = `✓ Valid ${provider} key`; } else { statusSpan.innerHTML = `✗ Invalid format`; } } function handleToneChange(value) { const customToneField = document.getElementById('customToneField'); if (value === 'custom') { customToneField.classList.remove('hidden'); } else { customToneField.classList.add('hidden'); } } function handleImageUpload(event) { const file = event.target.files[0]; if (file && file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = function(e) { const previewDiv = document.getElementById('uploadedImagePreview'); const previewImg = document.getElementById('previewImg'); previewImg.src = e.target.result; previewDiv.classList.remove('hidden'); }; reader.readAsDataURL(file); } } async function suggestStockImages() { const topic = document.getElementById('topic').value; const keywords = document.getElementById('keywords').value; if (!topic) { blogGenerator.showToast('Please enter a topic first', 'error'); return; } const suggestionsDiv = document.getElementById('stockImageSuggestions'); suggestionsDiv.innerHTML = '
    Loading image suggestions...
    '; suggestionsDiv.classList.remove('hidden'); // Generate 4 stock image suggestions using static.photos const imageUrls = [ `http://static.photos/technology/320x240/1`, `http://static.photos/office/320x240/2`, `http://static.photos/workspace/320x240/3`, `http://static.photos/abstract/320x240/4` ]; setTimeout(() => { suggestionsDiv.innerHTML = imageUrls.map((url, index) => `
    Suggestion ${index + 1}
    `).join(''); feather.replace(); }, 1000); } function selectStockImage(url) { document.getElementById('imageUrl').value = url; blogGenerator.showToast('Image selected!', 'success'); } function showApiGuide() { document.getElementById('apiGuideModal').classList.remove('hidden'); feather.replace(); } function closeApiGuide() { document.getElementById('apiGuideModal').classList.add('hidden'); } function updateOutlineSection(index, value) { blogGenerator.updateOutlineSection(index, value); } function removeOutlineSection(index) { blogGenerator.removeOutlineSection(index); } function fillTemplate(type) { const templates = { marketing: { topic: 'The Future of AI in Marketing', keywords: 'AI, marketing, automation, future', tone: 'informative', length: 'medium', language: 'en' }, security: { topic: 'Web Security Best Practices for Developers', keywords: 'security, web development, best practices', tone: 'technical', length: 'long', language: 'en' }, productivity: { topic: 'Top Productivity Tools for Solopreneurs', keywords: 'productivity, tools, solopreneur, business', tone: 'casual', length: 'short', language: 'en' } }; const template = templates[type]; Object.keys(template).forEach(key => { const element = document.getElementById(key); if (element) { element.value = template[key]; } }); blogGenerator.showToast('Template applied!', 'success'); } function regenerateBlog() { blogGenerator.regenerateBlog(); } function copyBlog() { blogGenerator.copyBlog(); } function downloadBlog(format) { blogGenerator.downloadBlog(format); } function toggleDarkMode() { const html = document.documentElement; const darkMode = !html.classList.contains('dark'); if (darkMode) { html.classList.add('dark'); } else { html.classList.remove('dark'); } localStorage.setItem('darkMode', darkMode); } function changeStep(direction) { blogGenerator.currentStep += direction; // Validate current step before moving forward if (direction > 0) { if (!blogGenerator.validateCurrentStep()) { return; } } blogGenerator.currentStep = Math.max(1, Math.min(blogGenerator.totalSteps, blogGenerator.currentStep)); blogGenerator.updateStepDisplay(); } function openHelpModal() { document.getElementById('helpModal').classList.remove('hidden'); feather.replace(); } function closeHelpModal() { document.getElementById('helpModal').classList.add('hidden'); } function showTooltip(field) { blogGenerator.showTooltip(field); } function toggleViewMode() { blogGenerator.toggleViewMode(); } function formatText(formatType) { blogGenerator.formatText(formatType); }