`;
lastGeneratedGaps = null;
switchTab(0);
return;
}
generateContentIdeas(gapKeywords);
});
async function generateContentIdeas(gapKeywords) {
showLoadingState(true);
try {
const systemPrompt = `You are an expert SEO Content Strategist. For each keyword provided, generate one compelling and actionable blog post title or content idea. Format your response clearly. For each keyword, prefix it with "Keyword:" and the idea with "Idea:". Separate each keyword-idea pair with a blank line.`;
const userQuery = `Generate content ideas for the following keywords:\n${gapKeywords.join('\n')}`;
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`;
const payload = {
contents: [{ parts: [{ text: userQuery }] }],
systemInstruction: { parts: [{ text: systemPrompt }] },
};
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) throw new Error(`API error: ${response.statusText}`);
const result = await response.json();
const ideasText = result.candidates?.[0]?.content?.parts?.[0]?.text;
if (ideasText) {
renderGaps(ideasText);
showFeedback('Analysis complete!', false);
switchTab(0);
} else {
throw new Error("No content received from API.");
}
} catch (error) {
console.error("Error generating ideas:", error);
showFeedback('Failed to generate ideas. Please try again.', true);
resultsPreview.innerHTML = ``;
} finally {
showLoadingState(false);
}
}
function parseAndRenderGaps(text) {
const pairs = text.split(/\n\s*\n/); // Split by one or more blank lines
let html = '';
const structuredGaps = [];
pairs.forEach(pair => {
const keywordMatch = pair.match(/Keyword:\s*(.*)/);
const ideaMatch = pair.match(/Idea:\s*(.*)/);
if (keywordMatch && ideaMatch) {
const keyword = keywordMatch[1].trim();
const idea = ideaMatch[1].trim();
structuredGaps.push({ keyword, idea });
html += `
`;
}
});
return { structuredGaps, html };
}
function renderGaps(text) {
const { structuredGaps, html } = parseAndRenderGaps(text);
lastGeneratedGaps = structuredGaps;
previewPlaceholder.classList.add('hidden');
resultsPreview.innerHTML = html;
}
function showLoadingState(isLoading) {
if (isLoading) {
previewPlaceholder.classList.add('hidden');
resultsPreview.innerHTML = '
Could not generate ideas. The AI service may be temporarily unavailable.
${keyword}
${idea}
';
generateBtn.disabled = true;
generateBtn.textContent = 'Analyzing...';
showFeedback('Finding gaps and generating ideas...', false, 0);
} else {
generateBtn.disabled = false;
generateBtn.textContent = 'Find Gaps & Generate Ideas';
}
}
downloadPdfBtn.addEventListener('click', () => {
if (!lastGeneratedGaps || lastGeneratedGaps.length === 0) {
showFeedback('Please generate an analysis before downloading.', true, 3000);
return;
}
showFeedback('Generating PDF...', false, 2000);
try {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ unit: 'pt', format: 'letter' });
const pageMargin = 72;
const pageWidth = pdf.internal.pageSize.getWidth();
const textWidth = pageWidth - (pageMargin * 2);
let cursorY = pageMargin;
// Title
pdf.setFont('Helvetica', 'bold');
pdf.setFontSize(20);
pdf.text('Content Gap Analysis Report', pageWidth / 2, cursorY, { align: 'center' });
cursorY += 40;
lastGeneratedGaps.forEach(gap => {
const keywordHeight = pdf.splitTextToSize(gap.keyword, textWidth).length * 16 * 1.2;
const ideaHeight = pdf.splitTextToSize(gap.idea, textWidth - 20).length * 12 * 1.2;
const totalHeight = keywordHeight + ideaHeight + 20;
if (cursorY + totalHeight > pdf.internal.pageSize.getHeight() - pageMargin) {
pdf.addPage();
cursorY = pageMargin;
}
pdf.setFont('Helvetica', 'bold');
pdf.setFontSize(14);
const keywordLines = pdf.splitTextToSize(gap.keyword, textWidth);
pdf.text(keywordLines, pageMargin, cursorY);
cursorY += keywordLines.length * 14 * 1.2 + 5;
pdf.setFont('Helvetica', 'normal');
pdf.setFontSize(11);
const ideaLines = pdf.splitTextToSize(gap.idea, textWidth - 20);
pdf.text(ideaLines, pageMargin + 20, cursorY);
cursorY += ideaLines.length * 11 * 1.2 + 25; // Add extra space between items
});
pdf.save('content-gap-analysis.pdf');
} catch(err) {
console.error("PDF Generation Error: ", err);
showFeedback("Error generating PDF.", true, 3000);
}
});
let feedbackTimeout;
function showFeedback(message, isError = false, duration = 3000) {
clearTimeout(feedbackTimeout);
feedbackMessage.innerHTML = message;
feedbackMessage.style.color = isError ? '#ef4444' : '#4a5568';
if (duration !== 0) { // allow indefinite message
feedbackTimeout = setTimeout(() => { feedbackMessage.innerHTML = ''; }, duration);
}
}
});
